anthropics / anthropics/cwc-long-running-agents
PreToolUse hooks silently ignored on current Claude Code: deprecated decision/reason schema
- Lenguaje dominante
- Shell
- Estrellas
- 678
- Forks
- 76
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Descripción
## Summary
On Claude Code 2.1.156 (and likely any version that has adopted the new PreToolUse hook schema), the harness scripts shipped in `claude-code-config/.claude/hooks/` are **loaded but silently ignored** when they try to block a tool call. The `/hooks` slash command shows them registered correctly under `Project` source — but their output JSON uses the *deprecated* top-level `decision`/`reason` schema that current Claude Code no longer honors for PreToolUse events.
Affected scripts: `kill-switch.sh`, `steer.sh`, `verify-gate.sh`. Two-line fix per script (swap the output JSON to `hookSpecificOutput.permissionDecision` / `permissionDecisionReason`). `track-read.sh` is unaffected (no decision emitted). `commit-on-stop.sh` is unaffected (Stop events still use the top-level schema per current docs).
The repo is otherwise excellent — the harness design (progress file, evaluator subagent, hook scripts) is exactly the right scaffold for long-running work. This is purely a schema-drift bug.
## Environment
- Repo `anthropics/cwc-long-running-agents` @ main (clone from 2026-05-22)
- Claude Code 2.1.156 (CLI)
- macOS arm64
- `.claude/` copied verbatim into a project root per the repo's README instructions
## Symptom
The kill switch fails to engage. Reproduction:
```bash
cd /path/to/project # where .claude/ from this repo has been copied
touch AGENT_STOP
claude
> list files in this folder
# Expected: tool call blocked with "Kill switch engaged" message.
# Actual: Claude lists files normally. AGENT_STOP is silently ignored.
```
Running `/hooks` inside Claude Code shows the hooks ARE loaded:
```
Hooks
5 hooks configured
1. PreToolUse (4) Before tool execution
2. PostToolUse After tool execution
3. PostToolUseFailure After tool execution fails
...
```
Running the kill-switch script directly emits the expected JSON:
```bash
$ touch AGENT_STOP && bash .claude/hooks/kill-switch.sh
{"decision":"block","reason":"Kill switch engaged: AGENT_STOP file exists. Agent is halted. Remove the file to resume."}
```
So the script works; Claude Code parses it; the *schema* is what's no longer honored.
## Root cause
Per the current Claude Code hooks reference (https://docs.claude.com/en/docs/claude-code/hooks), under "PreToolUse decision control" (verbatim from the docs):
> *"PreToolUse previously used top-level `decision` and `reason` fields, but these are deprecated for this event. Use `hookSpecificOutput.permissionDecision` and `hookSpecificOutput.permissionDecisionReason` instead. The deprecated values `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively. Other events like PostToolUse and Stop continue to use top-level `decision` and `reason` as their current format."*
The note implies the deprecated values *map* to the new ones, suggesting backward compatibility. In practice on Claude Code 2.1.156, the deprecated format is silently dropped — no warning, no block.
The three hook scripts in this repo that emit a PreToolUse block decision all use the deprecated schema:
- `claude-code-config/.claude/hooks/kill-switch.sh` (line 6-8)
- `claude-code-config/.claude/hooks/steer.sh` (line 12)
- `claude-code-config/.claude/hooks/verify-gate.sh` (line 23-25)
## Proposed fix
Three small diffs.
**`kill-switch.sh`:**
```diff
if [ -e "${AGENT_STOP_FILE:-./AGENT_STOP}" ]; then
cat <<'JSON'
-{"decision":"block","reason":"Kill switch engaged: AGENT_STOP file exists. Agent is halted. Remove the file to resume."}
+{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Kill switch engaged: AGENT_STOP file exists. Agent is halted. Remove the file to resume."}}
JSON
fi
```
**`steer.sh`:**
```diff
note=$(cat "$f")
reason=$(python3 -c 'import json,sys; print(json.dumps("OPERATOR STEERING: " + sys.argv[1] + "\n\nPause what you were about to do, incorporate this guidance, then continue toward the feature goal."))' "$note" 2>/dev/null) || exit 0
- printf '{"decision":"block","reason":%s}\n' "$reason"
+ printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":%s}}\n' "$reason"
: > "$f"
```
**`verify-gate.sh`:**
```diff
if [ ! -s "$log" ]; then
cat <<'JSON'
-{"decision":"block","reason":"Cannot modify the results file: no screenshot or console-log evidence has been Read this session. Open the evidence file with the Read tool first, then retry."}
+{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Cannot modify the results file: no screenshot or console-log evidence has been Read this session. Open the evidence file with the Read tool first, then retry."}}
JSON
exit 0
fi
```
No changes needed to `track-read.sh` (no decision emitted) or `commit-on-stop.sh` (Stop event — current docs note this event still uses the top-level `decision`/`reason` format).
## Verification
Applied the three diffs locally and re-ran the reproduction:
```bash
$ touch AGENT_STOP && bash .claude/hooks/kill-switch.sh | python3 -m json.tool
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Kill switch engaged: AGENT_STOP file exists. Agent is halted. Remove the file to resume."
}
}
$ claude
> list files in this folder
The agent is halted by a kill switch — an AGENT_STOP file exists in the
environment, which blocks tool execution. I can't list files (or run any
commands) until it's removed.
[Claude correctly refused the tool call, surfacing the reason verbatim.]
```
The harness now behaves exactly as the README describes.
## Workaround for affected users
Until a fix lands, apply the three diffs above to your local copy of `.claude/hooks/`. The originals can be backed up as `*.sh.orig` first. This is a one-time edit per project; the repo's other primitives (PROGRESS.md convention, evaluator subagent, `commit-on-stop.sh`) all work fine without modification.
## Why this matters
Without the fix, the most-touted harness primitives in the repo — the kill switch, the steering channel, and the verify-gate — are non-functional on current Claude Code. The README still describes them as working, so users following the README will land in a state where `touch AGENT_STOP` and updating `STEER.md` appear to do nothing. The harness scaffold sits on disk but provides no actual behavioral guarantees.
Happy to open a PR with the three diffs if useful.
---
*Reported with thanks. The harness pattern in this repo is the right scaffold for long-running agentic work, and the v2 hallucination-control playbook references it specifically. Worth keeping current with Claude Code's hook schema.*
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Evaluación
Este issue todavía no se ha evaluado.