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 --version → 0.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.
The shipped
convoy --versionsilently drops the git sha, so the rev a running convoy was built from is unrecoverable.Today
src/cli.tsresolves the sha by shelling out togitat runtime:This is the one of our three tools that tries to report build provenance, and it is the one that loses it:
.gitnext to the sources, so thecatchfires and the sha is dropped.convoy --version→0.2.0-ts.0, while the artifact it came from is stamped with a rev nobody can read back.src/cli.test.tsonly exercisesformatVersionin isolation, so both degradations are invisible to CI.Separately,
convoy doctorreports the version of every other tool it inspects but never its own — so adoctorpaste from a user carries no convoy provenance at all. There is no telemetry, so noservice.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:
baseVersionrevdirtysourceKindlocal(running from a checkout) ornix(packaged build)commitTsmachineVersion— 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--versionanddoctor:0.2.0+7d5211e — committed 4 days ago.Canonical names, so this stays consistent with our other tools:
__CLI_BUILD_STAMP__CLI_BUILD_STAMPScope
gitsubprocess with a build-time stamp. The rev must describe the built artifact, not whatever tree happens to be on disk.--versionprintsdisplayVersion;machineVersionstays available for structured output.convoy doctorleads with convoy's own build identity, so a pasted doctor report is self-identifying.Convoy already depends on
ptyandsmalltalk, 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.