Orchestrata Ultimate › Gates › commit-precheck

Gate: commit-precheck (SubagentStop)

Purpose Meet the commit gate while the worker still has the context, not at git commit after it has handed back
Where it runs SubagentStop hook ~/.claude/hooks/precommit_gate_subagent.sh (registered in ~/.claude/settings.local.json); the coordinator runs ~/bin/precommit-check itself before each commit; every builder brief's DONE WHEN
Trigger A subagent finishing after it edited .py files; by hand
Exact command ~/bin/precommit-check <repo> [file…] (no files: every changed or new .py against HEAD)
Blocks The hook returns {"decision": "block", "reason": ...} on a failure, at most 3 times per agent id. precommit-check exits with the gate's code and prints PRECHECK-OK or PRECHECK-FAIL <rc>
Ruling / ADR RUL-035 (hooks at user level, scoped to the cwd project); runs the RUL-008 review; RUL-041 (the coordinator checks, workers fix)

Why it exists

Mohamed, 2026-10-22: "you keep being blocked on lint every time ... when will you ever fix that". Workers' DONE WHEN ran only ruff and py_compile; the blocking gate, scripts/gate.py review-staged (code review plus test review, for example T4 module-scope fixtures), ran for the first time at git commit, after the worker had handed back (memory commit-gate-precheck).

How it works

~/bin/precommit-check (shell, read 2026-10-23):

  1. Resolve the repo root; collect the files (arguments, or git diff --name-only HEAD plus untracked .py); keep the ones that exist.
  2. No files: PRECHECK-OK (no .py changes). No scripts/gate.py: PRECHECK-OK (no commit gate in <repo>) (codetools passes trivially). No .venv/bin/python: PRECHECK-FAIL, exit 2, as the commit hook would.
  3. Put <repo>/.venv/bin first on PATH; ruff check --fix each file.
  4. Copy the index to /var/tmp/precheck-index.XXXXXX, git add the files into that copy under GIT_INDEX_FILE, and run env -u PYTHONPATH .venv/bin/python scripts/gate.py review-staged --repo-root <repo> against it. The real index is never changed; the copy is removed on exit.

Measured (memory): 0.4 s when nothing needs review, about 5 s for one test file.

~/.claude/hooks/precommit_gate_subagent.sh (shell, read 2026-10-23): reads agent_transcript_path and agent_id from the payload, collects the file_path of every Edit, Write and MultiEdit tool call on a .py file, groups them by git root (only roots with scripts/gate.py), runs precommit-check per root under timeout 240, and on any failure blocks with the last 40 lines of output and an instruction: "Fix every finding below, then re-run ~/bin/precommit-check <repo> <files> until it prints PRECHECK-OK, and only then finish. Do not weaken tests or add waivers to get past it." After 3 blocks it logs "gave up after 3 blocks" and lets the stop through. It never fails the session.

Diagram projects-orchestrata-ultimate-gates-commit-precheck-1

Evidence

~/.local/state/codetools/precheck-hook.log (13 lines on 2026-10-23): the pipe tests of 2026-10-22 21:48–21:50 (agent=probe-1) show rc=1 then rc=0 for the same file, the fail-then-fix path; real firings follow from 21:51 (py_edits=7, py_edits=4, py_edits=0). Memory commit-gate-precheck records the pipe tests: it caught T4, and passed a clean file and a read-only worker.

Typical failures and how to fix them

Symptom Cause Fix
The worker is blocked with T-rule or ruff findings the gate works The worker fixes them; see review-staged for each rule
PRECHECK-FAIL no project interpreter the worktree has no venv scripts/worktrees.py provision <path>
No SubagentStop lines in the log after a dispatch that edited files the hook is not firing Ask Mohamed to open /hooks (memory); check the file still exists
The hook file is gone deploy/install-claude-config.sh mirrors claude/hooks/ into ~/.claude/hooks/ with rsync --delete and deleted it on 2026-10-23 00:00 Restore it from the newest ~/.claude/.backup-*; durable fix (commit it into claude/hooks/) is open (memory claude-config-deploy-deletes-user-hooks)