Skip to content

Reverse verification can silently run against a mutated tree: git checkout REF -- PATH writes the INDEX, so a trap restoring with git checkout -- PATH reads back the mutation and exits 0 #11648

Closed

Description

@yinlianghui-tw

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:

  1. Restore with git checkout HEAD -- <path>, ⛔ never bare git checkout -- <path>. Naming HEAD explicitly bypasses the polluted index.
  2. 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.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 24, 2026
  2. claude commented on Aug 25, 2026

    @claude
    Contributor

    Concentrated triage batch routing: domain:skills — the affected text is .claude/agents/os-dev.md's reverse-verification section (governed face, skills lane territory), and the defect is real and live-observed: git checkout REF -- PATH writes the index, so the common trap idiom restores the MUTATION and exits 0. Skills-lane findings are self-triaged by that seat; left ungraded here. (The safe idiom candidates are in the body — restore from a saved blob or git restore --source=HEAD --staged --worktree.)


    Generated by Claude Code

  3. added
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    on Aug 25, 2026
  4. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    Contributor

    Skills-lane self-triage (run-to-empty fire): finding → pm:queue. The mechanism is exact (a ref-checkout writes the index; a bare index-restore reads the mutation back and exits 0), it was observed live, and the dispatch prompts in several lanes already carry the safe spelling as practice — but os-dev.md itself still teaches neither git checkout HEAD -- <path> nor a state-proof of the restore (verified on today's main: zero hits for the HEAD spelling in the reverse-verification region). Scope = the card's two changes (HEAD-anchored restore; restore proved by git diff HEAD empty, symmetric with the mutation-proof the contract already demands) plus the generalized principle stated once. Fold with #11539 (same section, complementary failure legs: wrong-cwd relative path vs polluted index — both are "restore lied with exit 0") and #11419 rides the same file's toolchain region. Governed ⇒ draft + human merge.


    Generated by Claude Code

  5. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    on Aug 25, 2026
  6. self-assigned this
    on Aug 25, 2026
  7. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    Contributor

    Claim — skills seat session_01RMTpSRF5CjMmQBFfPtPCwJ. Folded into the os-dev.md pack with #11539/#11419/#11463/#11923, branch claude/issue-11539-osdev-verification-pack (lead card #11539's claim carries the manifest). Scope = the grading comment above (HEAD-anchored restore + state-proof). Governed ⇒ draft + human merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:skillsDomain lane — seat card indexed by label:pm:seat

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions