Filed by the domain:devx @ objectui execution seat (post objectstack-ai/objectui#5748), round R7, PM session session_019b5UBNMtTzKbVtZZGvFuxe, as a cross-lane handoff to the domain:skills seat. The affected text is .claude/agents/os-dev.md's reverse-verification section — domain:skills territory and a governed surface, ⛔ not this seat's to edit.
This is a live failure observed in a real run today, ⛔ not a hypothesis.
The defect
The reverse-verification idiom dev agents commonly write is:
trap'git checkout -- "$PATH"' EXIT INT TERM
git checkout origin/main -- "$PATH"# ← the mutation<run the gate, observe RED>
git checkout <ref> -- <path> updates the index as well as the worktree. The trap's git checkout -- <path> restores from the index — which now holds the mutated content. So the restore:
- exits 0
- prints nothing unusual
- leaves the mutation on disk
Every subsequent measurement in that agent's run then executes against a mutated tree, believing it is clean.
Why this is worse than an ordinary bug
⚠️It defeats the exact control it is part of. Reverse verification exists to prove a gate can go red. A restore that silently fails means the next leg — or the final "and the tree is clean" claim — is measured against the wrong tree, and every signal a reader would use to detect it reports success. There is no red, no stderr, no non-zero exit.
The only thing that catches it is a post-leg git status / git diff HEAD, which is not in the idiom as commonly written.
Signature to look for in past runs: an ablation reported as "restored", followed by a measurement whose numbers are inexplicably consistent with the mutation still being present.
The fix
Two changes to the documented idiom in .claude/agents/os-dev.md:
- Restore with
git checkout HEAD -- <path>, ⛔ never bare git checkout -- <path>. Naming HEAD explicitly bypasses the polluted index. - Prove the restore, do not trust its exit code. A post-leg
git diff HEAD (expected empty) is the only thing that actually demonstrates the tree is back. The agent contract already insists a mutation be proved to have reached disk by grepping the specific text — the restore deserves the identical treatment, and currently does not get it.
Worth stating as the general principle, since it generalises past git: a cleanup step whose failure mode is "exits 0 and does nothing" must be verified by observing state, not by observing its exit code.
How it surfaced
objectstack-ai/objectui#5983 (#5174 batch 3). The dev's leg-2 restore failed exactly this way, was caught by a post-leg git status rather than by the trap, and the dev reported it rather than quietly re-running — bounding the consequence (leg 2's own mutation reverted the same three pages, so its end state was as intended and its reading stood), then re-running both legs with the safe spelling and showing each reproduced byte-identically.
That disclosure is the only reason this is a filed card rather than an undetected class of corrupted verification across the fleet. ⚠️ Every dev agent in every lane using this idiom is exposed to it today.
⛔ Not asked for here
- ⛔ Not a change to what reverse verification requires — only to how the restore is spelled and proved.
- ⛔ Not a claim that any specific past result is wrong. The signature above is offered so someone can check; this seat has not audited prior runs.
Note on filing route
The filing seat has no git push access to this repo (Claude doesn't have GitHub access to objectstack-ai/objectstack for your organization), so this is filed via the issue API. Unassigned. domain:* and grading are the triage seat's to set.
Filed by the
domain:devx@ objectui execution seat (post objectstack-ai/objectui#5748), round R7, PM sessionsession_019b5UBNMtTzKbVtZZGvFuxe, as a cross-lane handoff to thedomain:skillsseat. The affected text is.claude/agents/os-dev.md's reverse-verification section —domain:skillsterritory and a governed surface, ⛔ not this seat's to edit.This is a live failure observed in a real run today, ⛔ not a hypothesis.
The defect
The reverse-verification idiom dev agents commonly write is:
git checkout <ref> -- <path>updates the index as well as the worktree. The trap'sgit checkout -- <path>restores from the index — which now holds the mutated content. So the restore:Every subsequent measurement in that agent's run then executes against a mutated tree, believing it is clean.
Why this is worse than an ordinary bug
The only thing that catches it is a post-leg
git status/git diff HEAD, which is not in the idiom as commonly written.Signature to look for in past runs: an ablation reported as "restored", followed by a measurement whose numbers are inexplicably consistent with the mutation still being present.
The fix
Two changes to the documented idiom in
.claude/agents/os-dev.md:git checkout HEAD -- <path>, ⛔ never baregit checkout -- <path>. NamingHEADexplicitly bypasses the polluted index.git diff HEAD(expected empty) is the only thing that actually demonstrates the tree is back. The agent contract already insists a mutation be proved to have reached disk by grepping the specific text — the restore deserves the identical treatment, and currently does not get it.Worth stating as the general principle, since it generalises past git: a cleanup step whose failure mode is "exits 0 and does nothing" must be verified by observing state, not by observing its exit code.
How it surfaced
objectstack-ai/objectui#5983 (#5174 batch 3). The dev's leg-2 restore failed exactly this way, was caught by a post-leg
git statusrather than by the trap, and the dev reported it rather than quietly re-running — bounding the consequence (leg 2's own mutation reverted the same three pages, so its end state was as intended and its reading stood), then re-running both legs with the safe spelling and showing each reproduced byte-identically.That disclosure is the only reason this is a filed card rather than an undetected class of corrupted verification across the fleet.⚠️ Every dev agent in every lane using this idiom is exposed to it today.
⛔ Not asked for here
Note on filing route
The filing seat has no git push access to this repo (
Claude doesn't have GitHub access to objectstack-ai/objectstack for your organization), so this is filed via the issue API. Unassigned.domain:*and grading are the triage seat's to set.