anthropics / anthropics/cwc-long-running-agents

PreToolUse hooks silently ignored on current Claude Code: deprecated decision/reason schema

Abierto
#2 0 comentarios 0 reacciones 0 asignados Ver en GitHub
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.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.