Skip to content

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) - #152

Merged
robercano-ghbot merged 1 commit into
mainfrom
feat/issue-106-git-state-guard
Jul 17, 2026
Merged

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106)#152
robercano-ghbot merged 1 commit into
mainfrom
feat/issue-106-git-state-guard

Conversation

@robercano-ghbot

Copy link
Copy Markdown
Collaborator

Closes #106

Problem

Workers (implementer/orchestrator sessions) had no guard against running
git state-mutating commands against the shared MAIN checkout instead of
their own isolated worktree. On 2026-07-16 a driver's git checkout in
the main checkout failed mid-operation on a read-only-mounted agent file
and left main in a DETACHED HEAD on an unmerged commit for ~12h — with
a clean working tree throughout, so nothing in the existing telemetry
would have surfaced it.

Guard hook (guard-git-add.py, PreToolUse)

Extends the existing blanket-git add -A/commit -a guard with a second
check: blocks add/rm/mv/reset/switch/checkout <branch>/restore --staged when the command's effective git toplevel — honoring `cd

&&` and `git -C ` overrides in the command string — resolves to the MAIN checkout rather than the session's own `.claude/worktrees/` worktree, for **worker** sessions only.

Worker detection is two-signal, either sufficient on its own:

  • Primary: the RECODE_WORKER=1 marker env var (documented as a hard
    rule in .claude/agents/implementer.md and .claude/agents/orchestrator.md
    — set via each agent's own session env so the guard also protects an
    orchestrator that runs directly in the main checkout by cwd, not just an
    implementer that's always cwd-isolated).
  • Corroborating: the session cwd already sitting under
    .claude/worktrees/<name>/....

Owner sessions (neither signal present) are unaffected — default allow,
same best-effort spirit as the pre-existing guard: it only engages when
the sandbox is hardened, and never blocks non-hardened downstream
consumers.

Census signal (loop-census.sh)

Two new per-tick telemetry lines:

  • main_dirty=yes|no — is the MAIN checkout dirty, after excluding (a)
    sandbox /dev/null device-node masks and (b) the read-only-mounted
    .claude/agents/ and .claude/skills/setup/templates/ trees, which can
    legitimately lag behind HEAD in sandboxed sessions.
  • main_head=<branch>|detached — the MAIN checkout's current HEAD.
    Companion to main_dirty: the 2026-07-16 incident left main detached
    with a clean working tree the whole time, so main_dirty alone would
    never have caught it — this line closes that gap.

Prompt reinforcement

implementer.md / orchestrator.md each get an explicit "Git state (hard
rule)" section: ALL git operations happen inside the worker's own
worktree; a shared-branch need (or a branch "already checked out
elsewhere") is a re-scope signal to the orchestrator, never a license to
cd/git -C into the main checkout.

Tests

  • guard-git-add.test.sh (19 cases): owner-vs-worker × main-checkout-vs-
    own-worktree for every mutating subcommand, the non-mutating forms that
    must stay allowed, the -C/cd escape-hatch cases, and the
    corroboration-only (no marker) path.
  • loop-census.test.sh (+9 cases): clean/dirty/detached fixtures, the
    sandbox-mask exclusion, and the .claude/agents//.claude/skills/setup/ templates/ lag exclusion.

Both pass locally, along with the self-hosted gates
(GATES_FILE=.claude/self/gates.json bash .claude/scripts/gate.sh build|lint|test).

🤖 Generated with Claude Code

… (issue #106)

Prevent workers (implementer/orchestrator sessions) from mutating the
shared MAIN checkout's git state, and give the loop census a way to
detect it if it ever happens anyway.

- guard-git-add.py (PreToolUse hook): adds a second check alongside the
  existing blanket-`git add -A`/`commit -a` guard. Blocks
  add/rm/mv/reset/switch/checkout <branch>/restore --staged run by a
  worker session when the command's effective git toplevel (honoring
  `cd`/`git -C` overrides) resolves to the MAIN checkout rather than the
  worker's own `.claude/worktrees/<name>` worktree. Worker detection:
  primary signal is the RECODE_WORKER=1 marker env var (documented in the
  implementer/orchestrator agent defs), corroborated by the session cwd
  already sitting under a worktree path. Owner sessions (neither signal
  present) are unaffected — default allow, same best-effort spirit as the
  existing guard (never blocks non-hardened repos).

- loop-census.sh: two new per-tick telemetry lines.
    main_dirty=yes|no  — is the MAIN checkout dirty, excluding sandbox
      device-node masks and the read-only-mounted .claude/agents/ and
      .claude/skills/setup/templates/ trees (which can legitimately lag
      behind HEAD in sandboxed sessions).
    main_head=<branch>|detached — the MAIN checkout's current HEAD.
      Motivated by the 2026-07-16 incident: a driver's `git checkout`
      failed mid-operation against a read-only-mounted agent file and left
      main in a DETACHED HEAD on an unmerged commit for ~12h, with a
      CLEAN working tree throughout — main_dirty alone would have missed
      it entirely.

- implementer.md / orchestrator.md: explicit hard rule that ALL git
  operations happen inside the worker's own worktree, never the main
  checkout; a shared-branch need is a re-scope signal to the
  orchestrator, never a license to reach into the main checkout.

- Tests: guard-git-add.test.sh (19 cases — owner/worker x main/worktree,
  the `-C`/`cd` escape-hatch cases, and the corroboration-only path) and
  loop-census.test.sh (+9 cases covering main_dirty/main_head, the mask
  and read-only-mount exclusions, and the detached-HEAD signal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

9 similar comments
@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot

Copy link
Copy Markdown
Collaborator Author

feat(orchestrator): worker git-state guard + dirty-main census signal (issue #106) (not yet reviewed)

@robercano-ghbot
robercano-ghbot merged commit 9b06f6c into main Jul 17, 2026
9 checks passed
@robercano-ghbot
robercano-ghbot deleted the feat/issue-106-git-state-guard branch July 17, 2026 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module:harness Orchestrator machinery under .claude

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Orchestrator: workers must never mutate the MAIN checkout's git state — guard hook + dirty-main census signal

2 participants