0.11.0: a deny reaches the model, and says what to do next - #17
Merged
Conversation
Claude Code reads hookSpecificOutput.permissionDecisionReason into the
blocking error the model sees (hooks.ts:598-610); systemMessage goes to the
human. The cloud path set only systemMessage, so every ACP deny arrived at
the agent as the bare fallback "Hook PreToolUse:<tool> denied this tool" —
indistinguishable from a harness bug and impossible to re-plan on. Local
mode has always set both. That asymmetry is #690's "no reason" mystery seen
from the client end: the reason was written all along, into a field the
model cannot read.
Denies now carry the reason to the model, plus a steer that says what to do
next, because a denied call is one refused operation and not a refused task:
policy deny — continue with the rest; if you should have this capability,
draft a rule with acp_propose_rule for a human to approve
floor / gov — cannot be allowed by policy or approval; do not retry and
do not look for another route to the same effect
outage — availability, NOT a judgment about the call; nothing decided
the operation was wrong, so retry later
approval — a human is deciding; under codex it lands as a block, so
name the out-of-band route explicitly
Floors deliberately get no proposal path. Offering one would send the agent
to file something no human is able to approve.
Refs gatewaystack-connect#692, #627, #690
The gateway now types every verdict by what the agent should DO (HookResponse.kind): terminal, delegate, reformulate, retry. Reading that field beats pattern-matching the reason text — wording can change without anyone noticing it also changed the instruction the agent acts on, and the prose fallback only ever recognized the two prefixes it knew about, so an immutable-platform-rule deny was steering agents toward a proposal no human could approve. retry gets its own steer, which is the case that actually killed runs: a throttle is not a capability question, so the agent is told its task is not over, to retry the identical call shortly, and specifically NOT to rewrite it to dodge the limit or file a rule proposal about it. The regex fallback stays for gateways older than the field, and is tested so back-compat can't rot. 77 tests pass. Refs gatewaystack-connect#692
davidcrowe
added a commit
that referenced
this pull request
Aug 26, 2026
plugin.json and marketplace.json still said 0.10.3 while bin/govern.mjs ships PLUGIN_VERSION 0.11.0, so marketplace installs never see the deny-reaches-the-model update. Bump both to match.
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.
Client half of gatewaystack-connect#692. Pairs with gatewaystack-connect#766.
The bug
Claude Code reads
hookSpecificOutput.permissionDecisionReasoninto the blocking error the model sees (hooks.ts:598-610);systemMessagegoes to the human. The cloud path set onlysystemMessage.So every ACP deny arrived at the agent as the bare fallback
Hook PreToolUse:<tool> denied this tool— indistinguishable from a harness bug, and impossible to re-plan on. Local mode has always set both. That asymmetry is gatewaystack-connect#690's "no reason" mystery seen from the client end: the reason was written all along, into a field the model cannot read.What changed
Denies now carry the reason to the model, plus a steer saying what to do next — because a denied call is one refused operation, not a refused task:
acp_propose_rulefor a human to approveFloors deliberately get no proposal path — offering one sends the agent to file something no human is able to approve.
The steer is selected from the server's typed
kindfield rather than by pattern-matching the reason, so wording can change without silently changing the instruction the agent acts on. A regex fallback covers gateways older than the field, and is tested so back-compat can't rot.Verification
77 tests pass. New coverage for: the reason reaching the model at all, the propose path appearing only where it can be honoured, each typed steer, and the pre-
kindfallback.Note
The installer fetches
bin/govern.mjsfrommain, so this needs to land there before any machine picks it up.