Skip to content

fix: report the nightly's own version, not the release it anticipates - #149

Merged
thecodedrift merged 3 commits into
mainfrom
fix/nightly-cli-version
Aug 24, 2026
Merged

fix: report the nightly's own version, not the release it anticipates#149
thecodedrift merged 3 commits into
mainfrom
fix/nightly-cli-version

Conversation

@thecodedrift

@thecodedrift thecodedrift commented Aug 24, 2026

Copy link
Copy Markdown
Member

What

A nightly build now reports the version it is published as, rather than the released version its committed manifest declares — and the build refuses to emit a nightly whose two version-bearing values disagree.

The bug

Installing @taskless/cli-nightly@0.11.0-20260823220841xd9dee3d wrote:

"install": { "cliVersion": "0.10.2" }

…while the skills written beside it — same command, same run — pinned every invocation to @taskless/cli-nightly@0.11.0-20260823220841xd9dee3d. The manifest attributed the install to a version that never performed it.

That matters because install.cliVersion is what answers "what installed this?" — and that question gets asked precisely when someone is running a nightly to reproduce unreleased behavior.

Root cause

An ordering the nightly design chose on purpose. Per cli-nightly-builds, the version is stamped when the publishable artifact is produced and the committed manifest is deliberately left unchanged. But vite.config.ts baked __VERSION__ from that committed package.json. So the build asked a file a question it cannot answer, and got a confident wrong answer: the previous release.

Reproduced from a single local artifact before changing anything —

$ TASKLESS_NIGHTLY_VERSION=0.11.0-20260823220841xd9dee3d pnpm --filter @taskless/cli build:nightly
$ node packages/cli/dist/index.js --version
 Taskless CLI (taskless v0.10.2)          # ← wrong
$ node packages/cli/dist/index.js agent check | head -1
# Topic: check     (CLI v0.10.2 / topic v2)   # ← same wrong value
# …while every emitted invocation correctly reads @taskless/cli-nightly@0.11.0-…

I also confirmed the write path was not at fault: wizard/index.ts persists getCliVersion() and reads previousState.cliVersion only to render the diff, so nothing was preserving a stale value.

The fix

resolveCliVersion(environment, packageVersion) returns the stamped version for the nightly target and the committed version for every other. It delegates to the existing resolveNightlyVersion, so the no-stamp refusal is shared rather than restated — a nightly that cannot name itself fails the build instead of falling back, since that fallback is the wrong answer that produced this bug.

The stamp was already computed once and already passed to the build as TASKLESS_NIGHTLY_VERSION. This consumes a value that was there, rather than plumbing a new one.

The post-mortem guard (second commit)

The bug was not that the two values had different sources by design — it is that nothing ever compared them. One artifact announced one version and sent every agent to another, and nothing failed. The disagreement was visible only by comparing two outputs of the same build against each other, which is exactly what nobody does.

assertVersionConsistency now fails the build when a nightly's reported version and its embedded invocation name different versions.

It is tautological today — the fix above couples both to one stamp — and that is deliberate. It exists for the refactor that decouples them again: a second env var, a cached value, a default reintroduced "for local builds." Deriving from one source is not the same as being checked against it, which is exactly what this bug demonstrated.

It asserts on the resolved defines, not the emitted bundle: the build already holds the structured values, and re-deriving them from generated text would be the weaker tool (the styleguide's "verify build output in the build").

Verified by reproducing the bug against it — reverting the version to pkg.version and rebuilding:

Error: nightly build is inconsistent with itself: it reports version "0.10.2" but its
invocation is "npx @taskless/cli-nightly@0.11.0-20260823220841xd9dee3d". These must name
the same version — a build that announces one version and sends agents to another is
taskless/cli#148.

Had the guard existed, #148 could not have been emitted.

Blast radius

Four things read this define, and all four were wrong on a nightly: taskless --version, the %(CLI_VERSION)s recipe header, the cliVersion telemetry property, and install.cliVersion. All four are corrected together.

Unchanged: packages/cli/package.json; nightly-pack.cjs; and the assert-skill-versions build check, which compares skill frontmatter against the committed package.json — that is a claim about source files in the repository, not about what a build reports, and a nightly must not rewrite it.

Not corrected retroactively: manifests already written by a nightly still record the anticipated release. The next install fixes them.

Verification

nightly  → v0.11.0-20260823220841xd9dee3d   ✓ (was 0.10.2)
prod     → v0.10.2                          ✓ unchanged
init under a nightly → install.cliVersion: 0.11.0-20260823220841xd9dee3d  ✓
  • pnpm --filter @taskless/cli test — 699 passed, 52 files
  • pnpm typecheck, pnpm lint — clean
  • pnpm openspec validate --all --strict — 24 passed

Twelve new unit tests over an injected environment, matching how build-target.ts is already tested: the nightly reports its stamp, a missing stamp throws naming the env var, prod/dev/self plus an empty environment keep the committed version, the guard rejects a mismatch and names the issue, and the guard does not constrain targets where a version pin is meaningless.

Delivery shape

Single PR, two commits — the fix, then the guard that would have prevented it. The OpenSpec change is archived here, since a single PR lands spec, implementation, and archive together.

Fixes #148

🤖 Generated with Claude Code

https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms

…pates

Installing a nightly wrote `install.cliVersion: "0.10.2"` into
`.taskless/taskless.json` while the skills written beside it — same
command, same run — pinned every invocation to
`@taskless/cli-nightly@0.11.0-…`. The manifest attributed the install to
a version that never performed it.

The cause is an ordering the nightly design chose on purpose: the version
is stamped when the publishable artifact is produced, and the committed
manifest is deliberately left alone. But `__VERSION__` was baked from that
committed manifest, so `package.json` was asked a question it cannot
answer and answered confidently with the previous release.

`resolveCliVersion` now returns the stamped version for a nightly and the
committed one for every other target, and delegates to
`resolveNightlyVersion` so the no-stamp refusal is shared rather than
restated — a nightly that cannot name itself fails the build instead of
falling back, since the fallback is the wrong answer that produced this.

Reproduced first: one local nightly build reported v0.10.2 while pinning
the nightly invocation, which is both halves of the report from a single
artifact. Also confirmed the write path was not at fault — the wizard
persists `getCliVersion()` and reads the previous value only to render
the diff — so nothing was preserving a stale version.

Corrects `--version`, the `%(CLI_VERSION)s` recipe header, the telemetry
property, and `install.cliVersion` together; all four read this define.
Manifests already written by a nightly are not corrected retroactively;
the next install fixes them.

Fixes #148

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms
Copilot AI lite review requested due to automatic review settings August 24, 2026 00:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…ts version

Post-mortem guard for the bug in the parent commit. #148 shipped not
because the two version-bearing defines were derived from different
sources by design, but because nothing ever compared them: the invocation
read TASKLESS_NIGHTLY_VERSION while the version read package.json, so one
artifact announced v0.10.2 and sent every agent to v0.11.0-… . Nothing
failed. The disagreement was visible only by comparing two outputs of the
same build against each other, which is exactly what nobody does.

The fix couples them by construction, so the check is tautological today.
That is the point — it exists for the refactor that decouples them again:
a second env var, a cached value, a default reintroduced "for local
builds". Deriving from one source is not the same as being checked
against it, which is precisely what this bug demonstrated.

Asserts on the resolved defines rather than scanning the emitted bundle.
The build already holds the structured values, and re-deriving them from
generated text would be the weaker tool — the styleguide's "verify build
output in the build" rule.

Verified by simulating the decoupling: reverting the version to
`pkg.version` reproduces #148 exactly and the build now refuses it,
naming the issue and the env var both values derive from. Had this guard
existed, the bug could not have been emitted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms
@thecodedrift
thecodedrift force-pushed the fix/nightly-cli-version branch from 3324ec4 to d983e4b Compare August 24, 2026 00:47
@thecodedrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown

Claude finished @thecodedrift's task in 3m 6s —— View job


Review complete

Review mode: incremental — read 0 prior review thread(s) before reviewing. The prior-review data contained no inline threads and no substantive prior review (Copilot's review failed with a quota-limit error, and the only comment was the trigger itself), so this is effectively the first full review of this PR.

  • Read .prior-review.json
  • Understand the request (/review)
  • Gather diff and read changed files
  • Review for correctness, security, performance, tests, clarity
  • Post inline comments for concrete issues
  • Post final top-level summary

Assessment: solid, well-tested fix. One inline finding (spec consistency, not code correctness).

The core logic in packages/cli/scripts/build-target.ts and packages/cli/vite.config.ts is correct and matches the PR's own description precisely:

  • resolveCliVersion cleanly reuses resolveNightlyVersion's "never fall back" refusal instead of restating it — a nightly with no stamp still throws.
  • assertVersionConsistency is appropriately placed at the vite-config level (asserting on the resolved defines, not re-parsing emitted output), matching the styleguide's "verify build output in the build" rule, and is correctly a no-op for non-nightly targets.
  • I traced the only consumer of __VERSION__ (src/version.ts, src/globals.d.ts, commands/info.ts, prompts/recipes.ts, wizard/intro.ts, index.ts) — one define, one fix point, matches the "four things read this define" claim in the PR body.
  • Test coverage in test/build-target.test.ts looks thorough: all four non-nightly targets, missing-stamp refusal, the consistency guard's accept/reject paths (including the issue-number/env-var naming in the error message), and that the guard is a no-op for prod/dev/self.
  • No ordering bugs: cliInvocation and cliVersion are both derived from process.env before assertVersionConsistency runs, and nothing between those reads can mutate the env var.

[New] One finding, posted inline on openspec/specs/cli-nightly-builds/spec.md:249: the new nightly-specific version requirements contradict two existing, unmodified specs that state version resolution unconditionally comes from package.json:

  • openspec/specs/cli/spec.md:271 ("CLI version is injected at build time")
  • openspec/specs/analytics/spec.md:129-132 ("CLI version is resolved from package.json") — this one is notable because the PR explicitly claims to fix the cliVersion telemetry property, which is exactly what this scenario describes.

Neither file appears in the proposal's "Impact" (unchanged) list, so this looks like a genuine gap rather than an intentional scope decision, and openspec validate --strict wouldn't catch a cross-spec contradiction like this. Not a runtime bug — the code is correct — but worth a follow-up so the specs don't actively mislead the next reader.

No other correctness, security, performance, or test-adequacy issues found.

Comment thread openspec/specs/cli-nightly-builds/spec.md
…from package.json

Review caught a real gap: making a nightly report its stamped version
falsified two requirements this change never touched.

- `cli`: "CLI version is injected at build time" said `__VERSION__` is the
  version string read from `packages/cli/package.json`, full stop. Now
  scoped — the committed version for every target but nightly, which uses
  its stamp — with a scenario for the nightly case beside the existing one.
- `analytics`: "All capture calls include standard properties" described
  `cliVersion` as the package version read from `package.json`, and its
  scenario said the same. That one is pointed: this change's own claim to
  fix the `cliVersion` telemetry property is exactly what the scenario
  described, so the spec asserted the behavior being removed.

Both are recorded as MODIFIED deltas on the archived change, so the record
matches what shipped rather than only the permanent specs agreeing.

`openspec validate --strict` cannot catch a contradiction between two
specs — it validates each in isolation — so nothing would have failed
here. Swept the rest of `openspec/specs/` for the same claim; the
remaining hits are skill-version sync and the ast-grep schema pin, which
correctly mean the committed manifest and are untouched by this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cyga14bww8rmazH2XrF8ms
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @claude[bot] — "Claude finished @thecodedrift's task in 3m 6s…"
#149 (comment)

The spec-consistency finding was correct and is fixed in b061eb6; thread replied to and resolved.

It was worse than reported in one respect: analytics/spec.md was wrong in the requirement body as well as the scenario you cited — the body described cliVersion as "the @taskless/cli package version read from package.json". Since this PR claims to fix that exact telemetry property, the spec was asserting the behavior being removed.

Both corrections are also recorded as MODIFIED deltas on the archived change, so the change record matches what shipped rather than only the permanent specs agreeing. I swept the rest of openspec/specs/ for the same claim — the remaining hits are skill-version sync and the ast-grep schema pin, which genuinely mean the committed manifest.

Your note that --strict validates each spec in isolation and so cannot catch a cross-spec contradiction is the part worth keeping; that is why nothing failed here.

— AI Coding Agent

@thecodedrift
thecodedrift merged commit e788b11 into main Aug 24, 2026
3 checks passed
@thecodedrift
thecodedrift deleted the fix/nightly-cli-version branch August 24, 2026 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Running nightly with migration isn't updating the CLI version in taskless.json

2 participants