Skip to content

fix(pm): post-stamped exits non-zero when the platform did not store the body - #18690

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-18663-post-stamped-body-unstored-exit
Sep 17, 2026
Merged

os-justin merged 2 commits into
mainfrom
claude/issue-18663-post-stamped-body-unstored-exit

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #18663

Clause-②: no

The defect

scripts/pm/post-stamped.mjs already DETECTED the write the platform never stored — it read the
artefact back, classified the difference as mutated, printed the byte counts and the first
differing offset — and then returned 0. On the filed hit (a seat-post body refresh, 2026-09-17)
it sent 263,533 bytes, the platform stored 257,945 — the byte count of the version BEFORE that
write — with the first difference at byte 6792, where the new block began: the old body had been
kept whole and not one byte of the new one was there. A caller obeying every discipline this file's
own header prescribes — no pipe, $? captured before any pipe — was still told the body had
landed, and went on to post the comments that say the conclusion had already been written into it.
That is the opposite direction from the pipeline trap the header already warns about: that one is
about a caller throwing the code away, this was the tool handing out a zero.

The discriminating predicate, and the verdict fields it reads

One question decides the exit code: did every byte this act sent reach the platform?

All three benign classes answer yes by construction and are untouched — identical by definition,
trailing-newline-stripped gives up only newlines the platform does not keep, footer-appended
adds without removing. mutated is the only class the rule reads, and it splits in two:

export function sentBodyLanded(readBack) {
  if (!readBack || readBack.class !== 'mutated') return true;
  return readBack.footerReAnchored === true;
}

Two fields of readBackVerdict's own verdict object, and nothing else:

field what it is
readBack.class the existing declared vocabulary — unchanged, and classifyReadBack is not touched
readBack.footerReAnchored new, and a MEASUREMENT: the stored body is the sent body — or its newline-trimmed form — followed by EXACTLY PLATFORM_COMMENT_FOOTER. footerReAnchoring() is the one spelling of that comparison, shared with the classifier's comment-only footer-appended class rather than written a second time

⛔ Not a byte-count heuristic. The platform normalises blank lines around a trailing rule in both
directions, so a length comparison answers a different question: "stored is shorter" is neither
necessary (a re-anchored footer is LONGER) nor sufficient (an equal-length substitution loses just
as much). ⛔ And it does not need the pre-write body, which only --body ever holds: "the platform
kept the old one" is one INSTANCE of the class, not its definition — whatever is stored, a byte
that differs INSIDE the body this act sent is a byte this act did not get onto the platform. The
same predicate therefore covers the filed hit, a truncation, a sanitizer substitution, and a
--comment write.

--comment does share the verdict: main builds one readBackVerdict for both acts and passes
mode, so the same predicate judges both. A comment the sanitizer chewed now exits non-zero; the
comment footer append it is built to forgive is already clean one class earlier.

⛔ What did NOT change: classifyReadBack's classes and exemptions, the warning line, the offset
line, body_mutated in --json, what is SENT, and the stamp contract. There is no retry logic.

The one shape that must stay at 0, and does. A --body refresh where the platform appends its
58-byte footer classifies as mutated — the issue-body footer cell is unmeasured, and this PR does
not forgive it in the classifier. It exits 0 because nothing was lost. Measured on the fixture,
byte-for-byte the shape the seat hits on every seat-post refresh:

--- FOOTER RE-ANCHORED (control) --- exit=0 landed=true mutated=true
  ⚠️ read-back: sent 53 byte(s), stored 111 — the platform MUTATED the body. Read the artefact …
     first difference at byte 53: sent (end of body) | stored …\n\n---\n_Generated by [Claude Code](https://claude.ai/code)_
     …and that difference is EXACTLY the platform's footer, appended: every byte sent IS stored. Exit 0.

--- FILED SHAPE --- exit=4 landed=false mutated=true
  ⚠️ read-back: sent 53 byte(s), stored 48 — the platform MUTATED the body. Read the artefact …
     first difference at byte 20: sent …2 · the board as it stands now.\n | stored …1 · the board as it stood.\n
     ⛔ a byte this act sent is NOT the byte stored at that offset — the write did NOT land. Exit 4.

The exit register, before and after

code before after
0 written — including every read-back the tool could not vouch for written, and everything sent is on the platform
1 usage. Nothing written unchanged
2 the body broke the stamp contract, or a refresh would have voided an unread knock. Nothing written unchanged
3 PREREQUISITE NOT MET — no route, no token. No act at all unchanged
4 EXIT_NOT_STORED — written, and the platform did NOT store it

What a caller does with a 4 is in the header and in the tool's own stderr report: re-read the
artefact
; ⛔ do not retry blindly — the measured hit was a size refusal the platform never
reported, so an identical second write reproduces it exactly and a retry loop writes that failure
into the card over and over.

unreadable is deliberately NOT widened into the new code. "The platform returned no readable
body" is a failure to VERIFY, not a measured failure to store; it keeps its UNVERIFIED line and
its 0 until somebody measures what that cell means — the same reason the body-footer cell is not
forgiven in the classifier. The boundary is recorded in the header, not quietly crossed.

Pins

New battery in the file's --self-test, registered the way the ten existing ones are (battery()
opens it, t() attributes to the one most recently opened, the floor is evaluated last):

  • battery name, verbatim: 「the exit code: the read-back reaches $?, or it reaches nobody」
  • registered: 24 cases · pinned floor: 24 in SELF_TEST_BATTERIES
  • SELF_TEST_BATTERY_FLOOR: 10 → 11

What they pin: the filed shape (stored = the pre-write body, first difference inside the sent body)
⇒ 4 · a body-mode footer re-anchoring ⇒ 0, and still mutated with its offset · a re-anchoring over
a stripped trailing newline ⇒ 0 · a trailing-newline strip ⇒ 0 · an identical read-back ⇒ 0 · a real
mutation underneath an appended footer ⇒ 4 (the footer masks no loss) · a chewed --comment
read-back ⇒ 4, the --comment footer append ⇒ 0 · a truncation ⇒ 4 · unreadable ⇒ 0, not widened ·
the five register values distinct · the footer measured as exactly the declared bytes and not as a
58-byte delta (58 bytes of x answers null).

Ablation

From the committed fix, on disk, under a trap, with absolute paths: the predicate's last line
return readBack.footerReAnchored === true; replaced by return true; — which is exactly what the
tool did before this PR.

PRE  blob=d0dc60f58e4ca3b2155b5c05e312649fd9ed5d3e (== HEAD:scripts/pm/post-stamped.mjs)
anchor occurrences BEFORE: 1   anchor AFTER: 0   inject AFTER: 1
POST blob=428cfb46b3fd3d40037bc370a605ea9a2f3c5cef     # the edit provably reached the file
ABLATED EXIT=1 — ✗ post-stamped self-test: 6 of 257 case(s) failed, 0 floor problem(s).
  ✗ ⭐ THE FILED SHAPE: the platform kept the PREVIOUS body, so the sent bytes did NOT land
  ✗ ⭐ …and the exit code is the not-stored one, never OK
  ✗ ⭐ a real mutation UNDERNEATH an appended footer still exits non-zero — a footer masks no loss
  ✗ ⭐ the `--comment` read-back SHARES this verdict and is judged by it: a chewed comment does not exit 0
  ✗ ⭐ the rule is a predicate over the verdict's own fields, never over the byte counts
  ✗ ⛔ a TRUNCATION is a loss too: a stored body that stops short of the sent one does not exit 0
RESTORED blob=d0dc60f58e4ca3b2155b5c05e312649fd9ed5d3e
RESTORE PROVEN: blob == HEAD:scripts/pm/post-stamped.mjs and 'git diff HEAD' is empty
RESTORED EXIT=0 — ✓ post-stamped self-test: 257 cases pass across 11 batteries

Direction predicted before the run and observed: turns red, and only in the new battery — the ten
existing batteries stay green, so the ablation moves exactly what it claims to move. The six that
fall are the six that read exit/landed on a lost body; the footer-re-anchoring controls stay
green under the ablation by construction (a predicate that always answers "landed" still exits 0
there), which is why the filed-shape pins and not the controls are the ones that prove the fix.
Restore is proven by blob hash and by an empty git diff HEAD, not by an exit code; git checkout HEAD -- PATH names HEAD explicitly so a polluted index cannot serve the mutation back.

Self-test

$ node scripts/pm/post-stamped.mjs --self-test    # = pnpm check:pm-post-stamped
✓ post-stamped self-test: 257 cases pass across 11 batteries — offline, no network, no token.
EXIT=0

233 → 257 cases, 10 → 11 batteries.

Derived gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from the worktree
at 2f7df2abc (no hand-fed path list; change set = scripts/pm/post-stamped.mjs, 1 path).
29 derived, 29 run, 0 NOT-MEASURED, 0 UNRUN; every exit code captured with redirect-then-$?,
never across a pipe. Reconciled with --ran:

✓ dispatch-gates --ran: 29 derived famil(ies) accounted for — 29 run, 0 NOT-MEASURED
  (a DERIVED zero — all 29 recorded an exit code and none of them is 3).

All 29 exited 0:

node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pm-dispatch-gates :: exit 0
pnpm check:pm-post-stamped :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:watch-hint-literal :: exit 0

Plus the repo-wide scan the dispatch asked for, run whole and not narrowed:

$ pnpm lint    # = eslint . --no-inline-config, repo-wide
EXIT=0

And, beyond pnpm check:nul-bytes (exit 0 above), the author-side control-byte scan on the one
changed file: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' scripts/pm/post-stamped.mjs → no
hits (exit 1).

⛔ Not a complete account of what CI runs here: the derivation also names 53 artifact-roster
families, 11 declared-wide-population families, 14 pending-changeset families and 1 path-scheduled
CI job, each outside the 29 and each printed under its own heading by a run without --commands.

skip-changeset: scripts/pm/** is repo tooling and is in no package's files[] — nothing
published moves.

Acceptance notes

Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu


Generated by Claude Code

…the body

The read-back already detected the mutation and printed it; the exit code
stayed 0, so a caller obeying every discipline the header prescribes — no
pipe, `$?` checked — walked on as if the body had landed.

The verdict now carries an exit code. One question decides it: did every
byte this act sent reach the platform? The three declared normalisations all
keep the sent body whole and still exit 0. `mutated` splits in two on an
exact-bytes measurement (`footerReAnchored`, one spelling shared with the
classifier's comment-only footer class): a footer the platform re-anchored
took nothing away and keeps its 0, everything else is EXIT_NOT_STORED (4).

⛔ classifyReadBack is untouched — the class, the warning line, the offset
and `body_mutated` are all as pinned. What changed is what `$?` says.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin os-justin added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 17, 2026 — with Claude
@os-justin
os-justin marked this pull request as ready for review September 17, 2026 15:16
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 2fd23d0 Sep 17, 2026
39 of 43 checks passed
@os-justin
os-justin deleted the claude/issue-18663-post-stamped-body-unstored-exit branch September 17, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] post-stamped --body exits 0 on a write the platform did NOT store — the read-back catches it, the exit code does not

2 participants