anthropics / anthropics/cwc-long-running-agents

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

オープン
#2 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
Shell
スター
678
フォーク
76
PR マージ指標
30日以内にマージされた PR はありません

説明

## 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.*

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。