Skip to content

feat(setup): refuse to end a turn while a gate the agent started runs - #600

Merged
kixelated merged 2 commits into
mainfrom
quest/m0/token-waste/stop-hook
Nov 10, 2026
Merged

kixelated merged 2 commits into
mainfrom
quest/m0/token-waste/stop-hook

Conversation

@kixelated

@kixelated kixelated commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Finishes quest/m0/token-waste/stop-hook.md (deleted here). A workflow audit found 64 of 107 coordinator follow-ups telling a child it ended its turn with a build in the background, which kills the build and leaves the thread idle.

  • quest setup installs Claude Code Stop and SubagentStop hooks (quest hook). In a repository that uses Quest, they refuse to end the turn while a quest gate command the ending agent started still runs in the background. The refusal names each command and tells the agent to block on it.
  • quest setup also sets env.BASH_DEFAULT_TIMEOUT_MS to the cap (3300000, 55 minutes), so a long foreground command needs no timeout argument and no backgrounding. --remove takes out each timeout that still holds Quest's value.
  • How it tells whose gate it is: Claude Code (verified in 2.1.319's hook schema) passes background_tasks (id, type, command) to both hooks. That list covers the whole session, subagents included, so a shell task whose command runs quest gate counts as this agent's only when its own transcript (transcript_path for Stop, agent_transcript_path for SubagentStop) holds ID: <task id>. Claude Code writes that line when it backgrounds a command, and a task notification does not use that form. With no transcript, every running gate counts. The gate itself records nothing, so src/cli/gate.ts is unchanged and nothing conflicts with moq-dev/quest#585 or the gate FIFO admission work.
  • No trap: Claude Code lists a task only while it is in flight, so a refusal lasts no longer than the gate. quest gate already stops on cancellation, and Claude Code caps repeated Stop blocks.
  • Worktree hooks: quest worktree hook's SubagentStop skips releasing the subagent's checkout when the same check refuses the stop. Otherwise Claude would continue the subagent, and its running build, after the scratch was deleted.
  • usesQuest moves from hooks.ts to rules.ts next to the new runningGates, so worktree-hook.ts can use it without an import cycle.

The hook reaches a host only when its user reruns quest setup. As of 2026-11-09 the maintainer's ~/.claude/settings.json has none of Quest's hooks and no BASH_DEFAULT_TIMEOUT_MS.

Decisions

  • Whether the backgrounding prose leaves quest context (the quest's Goal says it should, but the hook covers only Claude Code with quest setup run, while Codex and Claude agents without it read the same assets/CONTEXT.md): ✅ (a) keep assets/CONTEXT.md unchanged (rejected: (b) drop the backgrounding sentence for everyone; (c) per-agent hook context). Settled by the maintainer in /quest-complete on 2026-11-10. This PR does (a).

Choices made without asking

  • Only quest gate commands are refused, matching the Goal. Other background commands, such as a dev server a session left running on purpose, pass.
  • A command counts as a gate when its text matches quest gate (also bun run --silent quest gate ...). A recipe that calls quest gate inside just test is not seen. The alternative was having the gate record its caller, which can't tell subagents apart, since they share CLAUDE_CODE_SESSION_ID and the env has no agent id.
  • Main-session Stop refuses too: quest context asks every agent to block on a gate in the same turn.
  • A Stop or SubagentStop input without background_tasks (older Claude Code) makes quest hook exit 1 with "upgrade Claude Code", following fail-loud. The worktree hook treats it as none, so worktree release still works on older versions.
  • A forked subagent's transcript starts with its parent's history, so a gate its parent backgrounded can hold the fork's stop. That lasts only until the gate ends.

Public API

  • quest setup writes two more hook entries (Stop, SubagentStop) and env.BASH_DEFAULT_TIMEOUT_MS. quest hook handles Stop and SubagentStop. No wire or on-disk format changes.

Validation

  • quest gate just check passed.
  • quest gate just test passed on 5fba112. New tests cover refusal for this agent's gate, ignoring another agent's gate and non-gate commands, the no-transcript case, the non-Quest repository case, missing background_tasks, the default timeout written and removed, and the worktree hook keeping a gated subagent's checkout.

Suggested follow-ups

  • Re-measure the follow-up rate (the quest's own note) after the maintainer reruns quest setup: recommend leaving it to the token-waste epic's closing re-measure, which already does this, rather than a new quest.
  • A Codex Stop hook (the gate records CODEX_THREAD_ID, and the hook refuses while a gate from that thread lives): recommend dropping it for now. The audited failures were Claude subagents under T3 Code, and Codex's handling of a turn ending on a running process is unverified. Plan it if the re-measure shows Codex threads doing this.

Caused by

No single PR caused this: it is agent behavior the prose rule did not prevent.

Models

Size: M
Implementer: Claude Code, Opus 5.5, medium effort

(Written by Claude Opus 5.5 with medium)

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1c64e-2df1-7fd0-98aa-ffe2dce8435e, round 1
  • Head: 5fba112
  • Base: origin/main at 20c4dce
  • Score: 9/10, No actionable bugs were found in the branch-only diff, and the implementation appears consistent with its intended behavior, though builds and tests were not run as instructed.
  • Findings: none

@pullfrog

pullfrog Bot commented Nov 10, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

quest setup installs Claude Code Stop and SubagentStop hooks that refuse to
end a turn while a background quest gate command the ending agent started
still runs, and sets BASH_DEFAULT_TIMEOUT_MS to the 55-minute cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title A turn cannot end on a running gate feat(setup): refuse to end a turn while a gate the agent started runs Nov 10, 2026
@kixelated
kixelated marked this pull request as ready for review November 10, 2026 16:34
@coderabbitai

coderabbitai Bot commented Nov 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 11 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5a88acd2-edd1-46cc-a6a5-ddf843fddb73
Commits

Reviewing files that changed from the base of the PR and between eaa0831 and 5fba112.

Files selected for processing: 9

  • README.md
  • SETUP.md
  • quest/m0/token-waste/stop-hook.md
  • src/cli/hooks.ts
  • src/cli/main.ts
  • src/cli/rules.ts
  • src/cli/worktree-hook.ts
  • test/hooks.test.ts
  • test/worktree.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head: 5fba112

No blocking code findings. The direction is consistent with the goal: filter in-flight gates by the ending agent's transcript, retain a subagent's checkout when that same check blocks its stop, and install/remove both timeout settings without removing user-customized values.

P3 — README.md:1781–1782: the new PATH sentence accidentally replaces the beginning of the existing body-inspection explanation, leaving “as for the worktree --body, a body= field”. Restore “It checks only a body on the command line itself:” before the flags; the intact PATH requirement is already below at lines 1789–1790. This preserves the explanation of which command bodies the enforcement hook actually inspects.

Verification limits: GitHub-only static review of the patch, surrounding hook paths and added tests. No tests, builds, or live Claude Code hook executions performed; the PR's claimed background-task/transcript schema and lifecycle were not independently exercised.

(Written by review (OpenAI))

@kixelated
kixelated added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit 9e0ca0f Nov 10, 2026
25 checks passed
@kixelated
kixelated deleted the quest/m0/token-waste/stop-hook branch November 10, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant