Summary
The hooks/pre-tool-use-edit hook is documented (in code comments and its own user-facing message) as advisory — but it uses exit 2, which Claude Code's hook contract treats as a block. Users see "Proceeding anyway -- the edit will happen" in the warning, then discover the edit silently didn't land.
This bites every summarize-task invocation because the framework meta-files it must touch (STATE.md, M###-S##-PLAN.md, M###-S##-T##-SUMMARY.md) are never listed in the task-plan's Files section by design, so the scope-drift gate always fires.
Repro
In any ytstack project with an active task (active_task: T01 in STATE.md):
- Run
/ytstack:summarize-task.
- The skill tries to
Write M###-S##-T##-SUMMARY.md and Edit M###-S##-PLAN.md + STATE.md.
- Each call triggers the hook with
Drift warning + Proceeding anyway -- the edit will happen.
- Verify on disk: the SUMMARY file does not exist, the checkbox is still
[ ], completed_tasks is unchanged, STATE.md status line is unchanged.
Observed on ytstack 0.1.4 via plugin cache at ~/.claude/plugins/cache/yesterday-public-plugins/ytstack/0.1.4/.
Root cause
hooks/pre-tool-use-edit:75-78 (the header comment) claims:
Both gates emit exit-2 warnings, not blocks. […] User-driven session with explicit scope creep is still valid work; the goal is to make it conscious.
…but Claude Code's PreToolUse hook contract treats exit 2 as a deny. So the "advisory" framing is contradicted by the actual return code. Concretely, hooks/pre-tool-use-edit:122:
echo " Proceeding anyway -- the edit will happen."
} >&2
exit 2
Either the message is wrong or the exit code is wrong.
Proposed fix
Two reasonable options, in order of preference:
Option A — make it truly advisory (matches stated intent): change exit 2 to exit 0 on the drift-warning path. The warning still surfaces via stderr; the tool call proceeds. This matches the in-skill flows (summarize-task, plan-task updating STATE.md, etc.) that legitimately touch meta-files outside the task scope.
Option B — whitelist framework meta-files: before the scope-drift check, exempt these paths so they never trigger:
case "$REL_PATH" in
.ytstack/STATE.md|.ytstack/M*-PLAN.md|.ytstack/M*-SUMMARY.md|.ytstack/M*-ROADMAP.md)
exit 0
;;
esac
Option A is simpler and preserves the "make scope drift visible" goal without the silent-failure footgun. Option B narrows the exemption but keeps the gate live for genuine source edits. Either fixes the immediate problem.
Workaround
Until fixed, in summarize-task flows, set active_task: none in STATE.md via a sed call (Bash bypasses the hook) before calling Edit/Write on STATE.md / S##-PLAN.md / T##-SUMMARY.md. With active_task=none, the scope-drift gate short-circuits and edits land normally.
Context
Discovered on SunoFlow (M001-S01-T01) while running /ytstack:summarize-task. Three consecutive Edit/Write calls were silently dropped despite the "proceeding anyway" message. Catalogue artifact (the actual T01 deliverable) had landed fine because it was listed in the task plan's Files section as Create; the meta-file updates that follow are what broke.
Summary
The
hooks/pre-tool-use-edithook is documented (in code comments and its own user-facing message) as advisory — but it usesexit 2, which Claude Code's hook contract treats as a block. Users see "Proceeding anyway -- the edit will happen" in the warning, then discover the edit silently didn't land.This bites every
summarize-taskinvocation because the framework meta-files it must touch (STATE.md,M###-S##-PLAN.md,M###-S##-T##-SUMMARY.md) are never listed in the task-plan'sFilessection by design, so the scope-drift gate always fires.Repro
In any ytstack project with an active task (
active_task: T01in STATE.md):/ytstack:summarize-task.WriteM###-S##-T##-SUMMARY.mdandEditM###-S##-PLAN.md+STATE.md.Drift warning+Proceeding anyway -- the edit will happen.[ ],completed_tasksis unchanged, STATE.md status line is unchanged.Observed on ytstack 0.1.4 via plugin cache at
~/.claude/plugins/cache/yesterday-public-plugins/ytstack/0.1.4/.Root cause
hooks/pre-tool-use-edit:75-78(the header comment) claims:…but Claude Code's PreToolUse hook contract treats
exit 2as a deny. So the "advisory" framing is contradicted by the actual return code. Concretely,hooks/pre-tool-use-edit:122:Either the message is wrong or the exit code is wrong.
Proposed fix
Two reasonable options, in order of preference:
Option A — make it truly advisory (matches stated intent): change
exit 2toexit 0on the drift-warning path. The warning still surfaces via stderr; the tool call proceeds. This matches the in-skill flows (summarize-task,plan-taskupdating STATE.md, etc.) that legitimately touch meta-files outside the task scope.Option B — whitelist framework meta-files: before the scope-drift check, exempt these paths so they never trigger:
Option A is simpler and preserves the "make scope drift visible" goal without the silent-failure footgun. Option B narrows the exemption but keeps the gate live for genuine source edits. Either fixes the immediate problem.
Workaround
Until fixed, in
summarize-taskflows, setactive_task: nonein STATE.md via asedcall (Bash bypasses the hook) before callingEdit/Writeon STATE.md / S##-PLAN.md / T##-SUMMARY.md. Withactive_task=none, the scope-drift gate short-circuits and edits land normally.Context
Discovered on SunoFlow (M001-S01-T01) while running
/ytstack:summarize-task. Three consecutive Edit/Write calls were silently dropped despite the "proceeding anyway" message. Catalogue artifact (the actual T01 deliverable) had landed fine because it was listed in the task plan's Files section asCreate; the meta-file updates that follow are what broke.