Gate the publishing act, not the subagent prompt - #473
Merged
EdbertChan merged 1 commit intoSep 12, 2026
Conversation
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_c113759f-a149-475f-b505-a71238a4eba9) |
agent-routing-guard decided from regexes over the spawn prompt. "carrying commits" parsed as an action because the noun test required a determiner, so deleting the word cleared the block without changing what the subagent would do; and its override check read only text blocks, so an answer given through AskUserQuestion never counted as approval. publish-act-guard reads the command the tool is about to run, which is a typed field, and blocks only when all three hold: the caller is a subagent, the command performs a publishing act at command position, and a liveness probe finds a reachable Invoker owner. No live owner means the subagent publishes, which is what the user asked for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UbWT7AQ3sJY49m13QPcom4 Change-Id: I53e191f4a21c16d42a1a345741108ead187f7687
EdbertChan
force-pushed
the
stack/EdbertChan/fix/publish-act-guard/gate-publishing-act-subagent-prompt--53e191f4
branch
from
September 12, 2026 06:35
d392aaf to
00851e0
Compare
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_98ae1dd3-9ecf-4a32-b48f-e66d0cfc61a1) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A guard meant to keep publishing work on Invoker was deciding from the words in a subagent prompt.
It blocked three spawns in one session. One cleared when the word "commit" was deleted from a sentence that forbade committing. Nothing about the work changed.
It also could not see an approval given through a question prompt, so an answer the user had already chosen read as no answer at all.
The two reads behind that, at
engine/hooks/agent-routing-guard/detect.pybefore this change:The noun test needs a determiner, so "carrying commits" fell through to the action branch.
_text_contentcollects only text blocks, so an answer arriving as a tool result was empty and never reached the override check.This replaces it with a gate on the command. A subagent that actually runs
git push, opens a PR, or pushes a stack is stopped while a live Invoker owner is reachable. When no owner answers, the subagent publishes, because Invoker cannot take the work.Review Claim
Approve deciding this gate from the command being run instead of from prompt wording.
Review Lane
behavior
Review Unit
engine-runtime
Safety Invariant
Three facts must all hold before anything is refused: the caller is a subagent, the command performs a publishing act at command position, and the liveness probe finds a reachable owner. Any read that cannot be made keeps the command running and prints why.
Slice Rationale
The new detector and the retirement of the old one are one claim: the gate moves from prose to the command. Landing them apart would leave two gates for the same decision, one of them known wrong.
Non-goals
No change to what counts as publishing work, to Invoker routing itself, or to any other hook. The main session is untouched: it was never gated and still is not.
Test Plan
Test Plan
cd engine/hooks/publish-act-guard && python3 -m unittest discover -s testspython3 -m unittest tests.test_installpython3 engine/skills/make-pr/scripts/preflight.py --base origin/mainDetector fixtures include
test_prompt_wording_cannot_trigger_or_clear_the_gate, which pins that an echoed sentence about "carrying commits" is silent while a realgit pushis refused.The fixtures caught two defects during the build:
git push --dry-runandcat scripts/safe-stack-push.mjsboth matched the first matcher and now do not.Install suite:
test_publish_act_guard_linked_and_bash_pretooluse_wired_for_claude ... okandtest_retired_agent_routing_guard_is_gone ... ok, exit code 0.Preflight:
unit engine-runtime: 20 file(s),neutral 2 file(s),check_hook_test_coverage: OK,ok preflight passed.Revert Plan
Revert Plan
git revert d392aaf./install.shto restore the old hook's settings entry, then restart the harness.$TMPDIRis disposable.Note
Medium Risk
Changes when subagents can push or open PRs during live Invoker sessions; misclassification of commands or liveness could block or miss routing, though UNCHECKED and non-subagent paths fail open.
Overview
Replaces prompt-based subagent blocking with command-based publishing gates. The retired
agent-routing-guardPreToolUse hook onAgentis removed entirely; newpublish-act-guardruns on Bash and only when a subagent is about to run a real publishing command (git push,gh pr create|merge|ready, mutatinggh apion/pulls, stack push scripts, etc.) whileinvoker-cli query capacityreports a live owner.The gate keys off parsed shell argv (dry runs and read-only neighbours stay silent), not subagent prompt prose—addressing false blocks from wording like “carrying commits” and override checks that missed non-text user answers. Main-session Bash is never gated; if Invoker is down or liveness is UNCHECKED, publishing is allowed (UNCHECKED is logged, not exit 2).
Install and docs:
install.shsymlinks and merges the new hook;install_claude_hook.pystrips staleagent-routing-guardsettings entries.docs/ecosystem.mdlistspublish-act-guard; install tests assert Bash wiring and that the old hook path and settings are gone.Reviewed by Cursor Bugbot for commit 00851e0. Bugbot is set up for automated code reviews on this repo. Configure here.