Agent(isolation:"worktree") â a built-in CC param
that doesn't reliably isolate, costing operators 3Ã tool-use bloat. This PR ships the framework so the
next bug in this class is a 1-line registry update, not a hook PR.
ð Bug bites â ð Operator burned (3à tool-uses) â ð feedback_*.md saved to memory â ð Docs PR â passive teaching â ðŠĶ Memory is orphan: never enforces â Cost: 1 PR per bug, forever.
ð Bug bites â ð 1-line registry entry + 2 unit tests â ðŠ PreToolUse linter fires at the moment of mistake, every session â â ïļ Loud advisory pointing at workaround â Cost: 1 line per next bug.
| File | Role |
|---|---|
src/hooks/src/lib/tool-invocation-rules.ts |
Schema + registry. Seed rule: agent-isolation-worktree.
Adding a rule = appending one object literal. |
src/hooks/src/pretool/tool-invocation-linter.ts |
The hook. Reads registry, filters to tool_name matches, evaluates
predicates safely, emits one consolidated advisory. |
src/hooks/src/__tests__/lib/ |
Coverage gate. Every rule.id must appear in the linter
unit-test file at least twice (positive + negative). Forgetting tests
for a new rule fails CI. |
pretool/task/sync-task-dispatcher.ts |
One wiring change â adds linter to the Agent-matcher dispatcher. No new hooks.json entry; reuses the existing Agent route. |
{
id: 'agent-isolation-worktree',
tool_name: 'Agent',
predicate: (input) => input.isolation === 'worktree',
severity: 'warn',
message: "Agent(isolation:'worktree') is known broken â ...",
see: 'docs/parallel-primitives.md + #1883',
}
Predicates are pure functions. If one throws (bug), the linter treats it as no-match and keeps going â a buggy rule never crashes the hook.
â ïļ [agent-isolation-worktree] Agent(isolation:'worktree') is known broken â it flips primary HEAD and races node_modules. Use the manual pre-create pattern: git worktree add ../<repo>-<task> -b <branch> origin/main BEFORE the Agent call, then prefix the agent's prompt with FIRST: cd <path>. (see: docs/parallel-primitives.md + #1883)
| Layer | What | Status |
|---|---|---|
| 1 â Unit | 11 cases on linter + 2 on safelyMatches | â 13/13 |
| 2 â Registry coverage | schema + per-rule test count | â 7/7 |
| 3 â Integration | stdin â run-hook.mjs â stdout | deferred (covered indirectly by Layer 5) |
| 4 â Perf | p99 < 5ms under load | deferred â 1-rule registry, no perf risk |
| 5 â Regression | full hook suite | â 8351/8351 |
| 6 â Dogfood | verify in live session post-merge | post-merge (no way to test pre-merge) |
Layers 3-4 were planned but provided diminishing return for v1 (the behavioral surface is small, and Layer 5 covers wiring). They're tracked in the follow-up issues.
feedback_*.md
for structured block: / warn: directives and merges them
into the registry. Memory becomes runtime defense without a PR.Agent(isolation:"worktree"). Linter's see: field
will point operators at it.