Problem
The worklist-guard hook (.claude/hooks/worklist-guard.py for Claude, app/shell/worklist-guard-codex.py for Codex) detects user-typed opt-out phrases (just do it, commit this directly, skip the worklist, etc.) by reading the latest user message in the Claude session transcript via last_user_text(transcript_path). That works for plain-chat opt-outs but misses opt-outs typed into an iterate feedback draft.
Concrete repro
- Worklist has an applied (TO COMMIT) item.
- User clicks Iterate with feedback text containing
just do it (e.g., make a test edit to README. just do it.).
- Bram drains the iterate via
feedbackRef to resources/feedback-drafts/<id>.md. The actual conversation message in the Claude session JSONL is just the structured payload: iterate: {"items":[{...}]} — it does NOT contain the literal just do it text.
- Agent attempts a file edit outside the worklist's coverage.
- Hook's
last_user_text reads the structured payload, finds no opt-out phrase, denies.
Observed live in #170 conversation: iterate feedback with explicit just do it ended up blocked by the guard; only retyping the phrase in plain chat unblocked the agent.
Proposed fix
When the latest user message in the transcript matches a structured iterate: payload shape, additionally read resources/feedback-drafts/<feedbackRef>.md for each item's feedbackRef and concatenate its content into the text the opt-out matcher sees.
The feedbackRef field is already part of the iterate payload shape (app/__shell/conventions.md); the hook just needs to parse it out of the JSON tail of the user message line, find each ref, and read the corresponding draft file from the project's resources/feedback-drafts/ directory.
Both hook scripts need the change:
app/__shell/worklist-guard.py (bundle template, installed at .claude/hooks/worklist-guard.py)
app/shell/worklist-guard-codex.py (Codex bundle template, installed at ~/.bram/codex-worklist-guard.py)
Same change pattern; share a helper if it factors cleanly.
Out of scope
- Changing the iterate payload shape itself.
- Reading
approved: / drop: feedback for opt-out — those payloads are structured authorization, not opt-out triggers. The opt-out matcher should remain narrowly scoped to in-flight free-text user direction, which today is plain-chat or iterate-feedback.
Refs
Problem
The worklist-guard hook (
.claude/hooks/worklist-guard.pyfor Claude,app/shell/worklist-guard-codex.pyfor Codex) detects user-typed opt-out phrases (just do it,commit this directly,skip the worklist, etc.) by reading the latest user message in the Claude session transcript vialast_user_text(transcript_path). That works for plain-chat opt-outs but misses opt-outs typed into an iterate feedback draft.Concrete repro
just do it(e.g.,make a test edit to README. just do it.).feedbackReftoresources/feedback-drafts/<id>.md. The actual conversation message in the Claude session JSONL is just the structured payload:iterate: {"items":[{...}]}— it does NOT contain the literaljust do ittext.last_user_textreads the structured payload, finds no opt-out phrase, denies.Observed live in #170 conversation: iterate feedback with explicit
just do itended up blocked by the guard; only retyping the phrase in plain chat unblocked the agent.Proposed fix
When the latest user message in the transcript matches a structured
iterate:payload shape, additionally readresources/feedback-drafts/<feedbackRef>.mdfor each item'sfeedbackRefand concatenate its content into the text the opt-out matcher sees.The
feedbackReffield is already part of the iterate payload shape (app/__shell/conventions.md); the hook just needs to parse it out of the JSON tail of the user message line, find each ref, and read the corresponding draft file from the project'sresources/feedback-drafts/directory.Both hook scripts need the change:
app/__shell/worklist-guard.py(bundle template, installed at.claude/hooks/worklist-guard.py)app/shell/worklist-guard-codex.py(Codex bundle template, installed at~/.bram/codex-worklist-guard.py)Same change pattern; share a helper if it factors cleanly.
Out of scope
approved:/drop:feedback for opt-out — those payloads are structured authorization, not opt-out triggers. The opt-out matcher should remain narrowly scoped to in-flight free-text user direction, which today is plain-chat or iterate-feedback.Refs
app/__shell/conventions.md— see the iterate /feedbackRefsection.decision=deny reason=no-coverage-no-opt-outdespite explicitjust do itin the feedback draft.