Release 1.2.3 — the capture prompt carries a window of the transcript, not the session - #874
Conversation
The prompt embedded the transcript whole, so the prompt was the session. On the reporter's machine a 67,981,436-byte transcript produced a 67,468,122-byte prompt — 99.3% of the file, and larger than any model can read, so the capture pipeline could not be completed and no record was ever written. The pipeline itself was fine: `tail -n 400` on the same transcript gave 537,250 bytes and the same command worked. That made it worse than a size. Prompt-only mode reports `outcome: "empty"`, `staged: false`, exit 0, so an operator who tried capture once on a real session got a prompt they could not use and no statement that anything was wrong. In the repository where it was measured, `stale` reported 1 record in 1000 commits with unattended capture already enabled — permission was never the obstacle. The prompt now carries the end of the transcript within a byte budget, 256 KiB by default and `COMMITLORE_TRANSCRIPT_BUDGET_BYTES` to change it. The end rather than the beginning: a decision is taken near the end of the session that implements it, and the diff being captured is that end. Three things the bound does not do. It does not renumber: the window's lines keep the numbers they have in the whole transcript, because verification reads the whole transcript and a locator renumbered from 1 would name a different line of the file it is checked against. It does not reach the hash: `source_hashes` and every quote check are still over the whole transcript, so a quote from outside the window still verifies and a caller passing the session it actually had is never told the transcript was substituted. And it does not stay quiet — the prompt says which lines it is showing and how many were left out, and `transcript_window` says the same to `capture --json` and to `commitlore_prepare_capture`. A bounded prompt that did not say so would be the old silence in a smaller package. One line of a JSONL transcript can hold an entire tool result and outrun the budget by itself. That line is shown from its end rather than dropped, and the window says so, because a window of no lines is worse than a window of one partial line. The byte slice never leaves a split codepoint at the front: a replacement character inside a quotable line is a character nobody can copy back. Where the boundary should be is not claimed. 256 KiB is comfortably readable by current models and carries far more than the ~537 KB slice measured to work; the reporter said they had not measured where the useful boundary is, and neither has this. Closes #873 Claude-Session: https://claude.ai/code/session_01USc9G3aJ1s8pnhWy5K5hLr Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings 1.2.2 (#870) under this change, so the release bump below sits on the version it follows.
Version fields, install pins and the changelog entry for 1.2.3. The release carries one fix: `capture` embedded the whole transcript in its prompt, so on a long session the prompt was larger than any model can read and no record was ever written (#873). The failure was silent — prompt-only mode reports outcome "empty", staged false, exit 0. The install pins move; the field-report paragraph in each README keeps saying v1.2.1, because that is the version the run it describes was made on. `dist/` is deliberately not in this branch. `canonical-merge.yml` rebuilds the bundle from the merged tree and refuses a pull request that touches it, so the committed bundle matches the source it lands with. Claude-Session: https://claude.ai/code/session_01USc9G3aJ1s8pnhWy5K5hLr Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CommitLore — record lintTrailers: clean — 3 commits in Active constraints for the paths this PR touchesLimits (221)
Ruled out (381)
Truncated: 356 lines omitted — the comment hit GitHub's 65000 character limit. Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR. |
|
Both jobs died at that step, ~60 seconds in. This branch is source-only, so the committed It is not fixable from this branch. What clears it: I have not re-run the failed jobs: the failure is deterministic and understood, not a flake. Green on this head: Locally: Generated by Claude Code |
Closes #873.
The defect
capture --json --transcript <path>returned apromptwith the transcript in it whole. Measured by the reporter:Larger than any model can consume, so the pipeline could not be completed and no record was ever written. The pipeline itself was fine — the reporter proved that by bounding the transcript and running the same command in the same repository:
Why this is worse than a size
It is silent. Prompt-only mode reports
outcome: "empty",staged: false, exit 0. An operator who tries capture once on a real session gets a prompt they cannot use and no statement that anything is wrong, and does not try again. In the repository where this was measured,stalereported 1 record in 1000 commits while roughly twenty commits in one recent session carried real decision context and produced none — with{"mode":"auto","unattended":true}already set, so permission was never the obstacle.The fix
The prompt carries the end of the transcript within a byte budget — 256 KiB by default,
COMMITLORE_TRANSCRIPT_BUDGET_BYTESto change it. The end rather than the beginning: a decision is taken near the end of the session that implements it, and the diff being captured is that end.Three things the bound deliberately does not do, each with a test that fails when its half is reverted:
L<start>-L<end>locator name a different line of the file it is checked against.source_hashesand every quote check are still over the whole transcript — a quote from outside the window still verifies, and a caller passing the session it actually had is never told the transcript was substituted.transcript_windowsays the same tocapture --jsonand tocommitlore_prepare_capture. A bounded prompt that did not say so would be the old silence in a smaller package.One JSONL line can hold an entire tool result and outrun the budget by itself. That line is shown from its end rather than dropped, and the window marks it partial, because a window of no lines is worse than a window of one partial line. The byte slice never leaves a split codepoint at the front — a replacement character inside a quotable line is a character nobody can copy back.
Release 1.2.3
Version fields in
package.json,package-lock.json(both root declarations),.claude-plugin/plugin.json,.codex-plugin/plugin.jsonandserver.json; install pins in four READMEs,install.shandinstall.ps1;CHANGELOG.mdentry.The "field report" paragraph in each README still says v1.2.1 — that is the version the run it describes was made on.
Verified locally
node scripts/check-release-version.mjs v1.2.3— consistent across all seven sourcesnpx tsc -p tsconfig.json --noEmitbash spec/verify.sh— 32 fixtures + protocol example sync + vocab tablenode scripts/check-readme-numbers.mjstest/capture-prompt-budget.test.ts— 10 passedharvest,harvest-verify,capture,capture-prepare,capture-verify,capture-pipeline-e2e,mcp,mcp-capture,backfill,capture-shadow— 302 passedreadme,manifest,compatibility-matrix,codex-plugin,check-release-version,release-publish-prerequisites— 181 passedNot claimed
Where the useful boundary is. 256 KiB is comfortably readable by current models and carries far more than the ~537 KB slice measured to work; the reporter said they had not measured where a good record stops needing context, and neither has this.
Also unchanged, and worth stating because automation is built on it:
outcomeisstaged,emptyorrejectedand all three exit 0, so anything driving capture must read--json— an exit-code check reads a rejected record as a success.No cross-provider review was run on this change.
dist/is deliberately absentcanonical-merge.ymlrefuses a pull request that touchesdist/orinstaller/canonical-artifact.json, and rebuilds the bundle from the merged tree. This branch is source-only, socheck (22.23.2)andcheck (24)will fail onartifact:verifyuntil that workflow is dispatched withpull_request_numberset to this PR — same as #871 → #872.🤖 Generated with Claude Code
https://claude.ai/code/session_01USc9G3aJ1s8pnhWy5K5hLr
Generated by Claude Code