You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Epic: Phase 1 — Claude Code hook-based enforcement on writ #124
Superseded by #1's collaboration-first architecture. Keep any landed hook/safety code that protects real integrity invariants, but do not revive the enforcement-first phase epic or universal never-merge model.
Owner steering — 2026-09-19
The current product direction is #1: parallel subagent collaboration and conflict recovery, not expanding universal merge bans. This overrides conflicting enforcement-first acceptance below.
Preserve completed identity/worktree/recovery fixes. Do not reopen completed children or add blanket Edit/Write/path-overlap vetoes, no-merge hooks, merge-queue bans, or repeated approval machinery for their own sake. RM-145 owns simplifying shared docs and integration policy; #136 owns recovery-compatible coordination state. Local integration in an assigned worktree is distinct from a GitHub PR merge. The missing GitHub protection configuration is an operator concern to report accurately, not a reason to block all collaboration work.
Existing sessions and PRs stay in place. Historical scope follows for traceability; no new worker launch or repository-setting mutation is requested by this update.
Important
Product goal (2026-09-05):writ (formerly worktrees-hives) is the enforcement and admission-control layer for agent fleets. Task assignment is commoditized — Claude Code agent teams, /batch, Cursor /multitask and Codex already assign and isolate work. What nothing enforces is safe concurrent writing, and nothing arbitrates contention — whether a second agent may take a path or a work item already taken. writ enforces at the git mutation boundary through Claude Code hooks (a PreToolUse exit 2 cannot be overridden, even by another hook's permissionDecision: "allow"), leases worktrees it did not create, and never merges. Prefer work that hardens that boundary over creating worktrees or re-implementing task assignment.
Why this epic was rewritten
This was the stabilization epic. Its integration queue described PRs #130, #131, #132 as open at specific SHAs and #133/#137 as pending — all merged or closed by 2026-09-05. Its exit criteria pointed at #108 as the next epic; #108 is closed NOT_PLANNED. The stabilization goal succeeded: the PR queue is drained, 0 open PRs.
It is now the Phase 1 delivery epic for the pivot in #1.
Goal
Put the Rust enforcement core into the real production path as Claude Code hooks — on current crate names, with no rename and no deletion.
Renaming and demolition are deliberately excluded from this phase. If hooks land in the same PR as a rename, a failure is indistinguishable between "hooks broken" and "rename fallout." wh / wh-core / WH_* stay exactly as they are so every failure here is attributable to the new code. Rename is Phase 2, python/ deletion is Phase 3 — both tracked under milestone M2.
New CLI surface
wh hook <event> # reads hook JSON on stdin, exits 0 or 2
wh verify <path> # verify worktree identity + exact base
wh lease grant|check|release|list
wh install # write the hook block into .claude/settings.json
wh hook is one entry point dispatching on hook_event_name:
Event
Behavior
PreToolUse, matcher Bash
Parse tool_input.command, run existing git_safe/gh_safe validation, exit 2 + stderr reason on reject
if conditions Bash(git *) and Bash(gh *)only. Expand only after false-positive burn-in on real sessions. A hook that blocks legitimate work gets uninstalled, and an uninstalled hook enforces nothing. Editing tools (Edit/Write) are Phase 5, after burn-in establishes that a blocking hook can be trusted.
Minimal lease store lands here, not in Phase 4
Without it, WorktreeCreate "records a lease" into nothing and we have reproduced the Markdown control plane in Rust. Scope for this phase:
rusqlite with the bundled feature — compiles SQLite from source, preserving single-static-binary distribution.
One file. A leases table (repo, branch, worktree path, owner, mode, TTL, heartbeat) and an agents table.
Path scopes, budget columns, and the remaining tables are Phase 4; the schema must not need a rewrite to add them. Reserve nullable max_files, max_churn, max_fix_cycles, and fix_cycles on leases now — see [writ] Advisory change-growth budgets for collaborative agents #167.
SQLite's own locking supersedes the hand-rolled fcntl.flock + atomic-rename pattern in watchlist.py — do not port it.
crates/wh-core/src/state.rs is replaced by this store in this phase. It is currently a reader for a file with no writer, so wh status and wh jobs always return empty in real use.
Reproduced blockers (children, keep)
[Enforce] Reject ambiguous unqualified start-point refs #139— ambiguous symbolic start points (P1). A same-named branch and tag at different commits make Git exit 0 with an ambiguity warning on stderr; the implementation ignores stderr and can create from the tag when the caller meant the branch.
[writ] Byte-safe registration for harness-owned worktrees #140— newline-safe registration parsing (P2). Line-oriented parsing of git worktree list --porcelain corrupts valid POSIX paths containing newlines. Needs the documented NUL-delimited --porcelain -z contract. The hook path reads registrations it did not write, so this matters more now.
[Hook] Verified worktree reuse so hooks can reattach a reclaimed branch #141— verified worktree reuse (P2).reject_unproven_resume hard-rejects every pre-existing branch, so there is no supported create → cleanup → reclaim path. Claude Code reuses worktrees by name; this is precisely why the production loop bypasses wh entirely. This is the highest-priority child — Phase 1 does not function without it.
Additional Phase 1 fixes
identity.rsis a privatemod(lib.rs:9) with 0 tests and holds the strongest code in the repo — the leading_hex_oid_prefix abbreviated-OID smuggling defense (identity.rs:85-153). Make it pub, test it.
worktree.rs:271 prunehas no sandbox check — it accepts any --repo path.
worktree.rs:210-213 listreturnsrepo_root: PathBuf::new()andstart_commit: None — listings carry no identity, so nothing downstream can verify a listed worktree. The hook path needs both populated.
supervisor.rs--max-parallelis dead in the CLI path (documented at main.rs:120-124: "in this process… does not coordinate across separate wh processes"). Back it with the lease store or drop the flag.
Operating rules (retained — these worked)
WIP limit: at most two active implementation branches.
Native gates are canonical: repository tests, formatting, linting, Clippy, and builds appropriate to the changed surface.
External analyzers are advisory until reproduced. A bot commenting does not create a blocker.
No issue per bot comment. Deduplicate by root cause; create a child only for an independently deliverable contract.
Prefer deletion and pure decision functions over new flags and duplicated policy prose.
wh install writes a working hook block into .claude/settings.json.
SQLite lease store exists; a WorktreeCreate row is verifiable by direct query, and WorktreeRemove releases it.
identity.rs is public and covered by tests.
End-to-end in a live Claude Code session: git push --force, git merge, gh pr merge, gh api all blocked with the reason visible to the model.
A competing hook's permissionDecision: "allow" cannot override the exit-2 block.
False-positive burn-in complete: a normal working session blocks no legitimate command.
cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace pass on the resulting main.
Crate names, env vars, andpython/are untouched. Any rename or deletion in this phase is out of scope.
Caution
wh install writes to .claude/settings.json, which is user-owned config outside the repo. Burn-in runs against a throwaway repo with a scoped settings file before anything touches a live multi-repo working configuration.
changed the title [-]Epic: Phase 1 — hook-based enforcement on current crate names[/-][+]Epic: Phase 1 — Claude Code hook-based enforcement on writ[/+]on Sep 13, 2026
Phase 1 hook slice is in propose-only PR #171 at 47e3202ad6f546aef1208af5873b4335bfe67447.
writ hook + writ install + SQLite lease skeleton landed. #141/#139/#140 are included. Budget columns reserved, not enforced (#167). Live Claude Code e2e was not available in the implementation VM.
Current disposition — 2026-09-21
Superseded by #1's collaboration-first architecture. Keep any landed hook/safety code that protects real integrity invariants, but do not revive the enforcement-first phase epic or universal never-merge model.
Owner steering — 2026-09-19
The current product direction is #1: parallel subagent collaboration and conflict recovery, not expanding universal merge bans. This overrides conflicting enforcement-first acceptance below.
Preserve completed identity/worktree/recovery fixes. Do not reopen completed children or add blanket Edit/Write/path-overlap vetoes, no-merge hooks, merge-queue bans, or repeated approval machinery for their own sake. RM-145 owns simplifying shared docs and integration policy; #136 owns recovery-compatible coordination state. Local integration in an assigned worktree is distinct from a GitHub PR merge. The missing GitHub protection configuration is an operator concern to report accurately, not a reason to block all collaboration work.
Existing sessions and PRs stay in place. Historical scope follows for traceability; no new worker launch or repository-setting mutation is requested by this update.
Important
Product goal (2026-09-05):
writ(formerlyworktrees-hives) is the enforcement and admission-control layer for agent fleets. Task assignment is commoditized — Claude Code agent teams,/batch, Cursor/multitaskand Codex already assign and isolate work. What nothing enforces is safe concurrent writing, and nothing arbitrates contention — whether a second agent may take a path or a work item already taken.writenforces at the git mutation boundary through Claude Code hooks (aPreToolUseexit 2 cannot be overridden, even by another hook'spermissionDecision: "allow"), leases worktrees it did not create, and never merges. Prefer work that hardens that boundary over creating worktrees or re-implementing task assignment.Why this epic was rewritten
This was the stabilization epic. Its integration queue described PRs #130, #131, #132 as open at specific SHAs and #133/#137 as pending — all merged or closed by 2026-09-05. Its exit criteria pointed at #108 as the next epic; #108 is closed
NOT_PLANNED. The stabilization goal succeeded: the PR queue is drained, 0 open PRs.It is now the Phase 1 delivery epic for the pivot in #1.
Goal
Put the Rust enforcement core into the real production path as Claude Code hooks — on current crate names, with no rename and no deletion.
Renaming and demolition are deliberately excluded from this phase. If hooks land in the same PR as a rename, a failure is indistinguishable between "hooks broken" and "rename fallout."
wh/wh-core/WH_*stay exactly as they are so every failure here is attributable to the new code. Rename is Phase 2,python/deletion is Phase 3 — both tracked under milestone M2.New CLI surface
wh hookis one entry point dispatching onhook_event_name:PreToolUse, matcherBashtool_input.command, run existinggit_safe/gh_safevalidation, exit 2 + stderr reason on rejectWorktreeCreateWorktreeRemoveSubagentStart/SubagentStopMatcher scope starts deliberately narrow
ifconditionsBash(git *)andBash(gh *)only. Expand only after false-positive burn-in on real sessions. A hook that blocks legitimate work gets uninstalled, and an uninstalled hook enforces nothing. Editing tools (Edit/Write) are Phase 5, after burn-in establishes that a blocking hook can be trusted.Minimal lease store lands here, not in Phase 4
Without it,
WorktreeCreate"records a lease" into nothing and we have reproduced the Markdown control plane in Rust. Scope for this phase:rusqlitewith thebundledfeature — compiles SQLite from source, preserving single-static-binary distribution.leasestable (repo, branch, worktree path, owner, mode, TTL, heartbeat) and anagentstable.UNASSIGNED | WRITER_LOCKED | REVIEW_ONLY | NEEDS_HUMAN | BLOCKED | MERGE_READY.max_files,max_churn,max_fix_cycles, andfix_cyclesonleasesnow — see [writ] Advisory change-growth budgets for collaborative agents #167.fcntl.flock+ atomic-rename pattern inwatchlist.py— do not port it.crates/wh-core/src/state.rsis replaced by this store in this phase. It is currently a reader for a file with no writer, sowh statusandwh jobsalways return empty in real use.Reproduced blockers (children, keep)
git worktree list --porcelaincorrupts valid POSIX paths containing newlines. Needs the documented NUL-delimited--porcelain -zcontract. The hook path reads registrations it did not write, so this matters more now.reject_unproven_resumehard-rejects every pre-existing branch, so there is no supported create → cleanup → reclaim path. Claude Code reuses worktrees by name; this is precisely why the production loop bypasseswhentirely. This is the highest-priority child — Phase 1 does not function without it.Additional Phase 1 fixes
identity.rsis a privatemod(lib.rs:9) with 0 tests and holds the strongest code in the repo — theleading_hex_oid_prefixabbreviated-OID smuggling defense (identity.rs:85-153). Make itpub, test it.worktree.rs:271 prunehas no sandbox check — it accepts any--repopath.worktree.rs:210-213 listreturnsrepo_root: PathBuf::new()andstart_commit: None— listings carry no identity, so nothing downstream can verify a listed worktree. The hook path needs both populated.supervisor.rs--max-parallelis dead in the CLI path (documented atmain.rs:120-124: "in this process… does not coordinate across separatewhprocesses"). Back it with the lease store or drop the flag.Operating rules (retained — these worked)
Exit criteria
wh hookhandlesPreToolUse,WorktreeCreate,WorktreeRemove,SubagentStart,SubagentStop.wh installwrites a working hook block into.claude/settings.json.WorktreeCreaterow is verifiable by direct query, andWorktreeRemovereleases it.identity.rsis public and covered by tests.git push --force,git merge,gh pr merge,gh apiall blocked with the reason visible to the model.permissionDecision: "allow"cannot override the exit-2 block.cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspacepass on the resultingmain.python/are untouched. Any rename or deletion in this phase is out of scope.Caution
wh installwrites to.claude/settings.json, which is user-owned config outside the repo. Burn-in runs against a throwaway repo with a scoped settings file before anything touches a live multi-repo working configuration.Parent: #1
Children: #139, #140, #141, #127, #134, #136
Kernel contract: #22, #81 · Classifier: #107 · Schema reservation: #167
Superseded: the #108 graph epic and its children, closed
NOT_PLANNED2026-09-05.Rewritten by Claude Opus 5, 2026-09-05.