Skip to content
This repository was archived by the owner on Jul 24, 2026. It is now read-only.
This repository was archived by the owner on Jul 24, 2026. It is now read-only.

Adopt the shared build-identity contract: shipped --version silently drops the git sha #83

Description

@schickling-assistant

The shipped convoy --version silently drops the git sha, so the rev a running convoy was built from is unrecoverable.

Today

src/cli.ts resolves the sha by shelling out to git at runtime:

async function versionString(): Promise<string> {
  const root = dirname(dirname(fileURLToPath(import.meta.url)));
  let semver = "0.0.0";
  try {
    semver = (JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version as string | undefined) ?? semver;
  } catch { /* package.json unreadable — keep the fallback */ }
  let sha: string | null = null;
  try {
    const r = await run("git", ["rev-parse", "--short", "HEAD"], { cwd: root });
    sha = r.ok ? r.stdout.trim() || null : null;
  } catch { /* git missing or not a repo — omit the sha */ }
  return formatVersion(semver, sha);
}

This is the one of our three tools that tries to report build provenance, and it is the one that loses it:

  • In a packaged build there is no .git next to the sources, so the catch fires and the sha is dropped. convoy --version0.2.0-ts.0, while the artifact it came from is stamped with a rev nobody can read back.
  • In a dev checkout it reports the developer's working-tree HEAD, not the revision the running code was built from. Those diverge the moment you check out another branch.
  • src/cli.test.ts only exercises formatVersion in isolation, so both degradations are invisible to CI.

Separately, convoy doctor reports the version of every other tool it inspects but never its own — so a doctor paste from a user carries no convoy provenance at all. There is no telemetry, so no service.version.

Proposed: adopt the shared build-identity contract

Model the same canonical fields we use in our other CLIs, and derive two outputs from them:

field meaning
baseVersion package version from the manifest
rev short VCS revision of the built source
dirty uncommitted changes present at build/run time
sourceKind local (running from a checkout) or nix (packaged build)
commitTs commit timestamp
  • machineVersion — stable and parseable, for telemetry, APIs, logs, and exact comparison: 0.2.0, 0.2.0+7d5211e, 0.2.0+7d5211e-dirty. No prose, no relative time.
  • displayVersion — human-facing, for --version and doctor: 0.2.0+7d5211e — committed 4 days ago.

Canonical names, so this stays consistent with our other tools:

  • source placeholder: __CLI_BUILD_STAMP__
  • runtime env var for source runs: CLI_BUILD_STAMP

Scope

  • Replace the runtime git subprocess with a build-time stamp. The rev must describe the built artifact, not whatever tree happens to be on disk.
  • --version prints displayVersion; machineVersion stays available for structured output.
  • convoy doctor leads with convoy's own build identity, so a pasted doctor report is self-identifying.
  • Add a test that asserts a packaged build reports a non-degraded stamp — the current failure mode is exactly the one the existing test cannot see. A zeroed or sha-less stamp in a shipped artifact should fail the build, not degrade quietly.
  • Add the placeholder and resolver here. This repo is consumed as a plain source (not as a flake), so the packaging that substitutes the stamp lives in the consuming build.

Convoy already depends on pty and smalltalk, so if we would rather have one implementation than three, this is the dependency edge along which a shared version module could be lifted. Worth deciding once, before the third copy exists.

Sibling issues adopting the same contract: compoundingtech/pty#107, compoundingtech/smalltalk#103. The three should land the same field names and the same two derived outputs so a version string means the same thing across the toolchain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions