Structured attribution of user permission denials to the parent agent (denial state visible to the orchestrator)
Problem
In a multi-agent orchestration setup (a parent/orchestrator agent delegating to subagents), a user's permission denial made in response to a subagent's tool call has no reliable, attributed path back to the parent agent. It arrives, if at all, only as unstructured prose inside the subagent's own final report — with no marker distinguishing "this is the user's denial" from "this is the subagent's own commentary."
This produces a repeatable failure pattern. I observed it twice, on consecutive days, in an otherwise-instrumented setup:
- A subagent proposed a design choice; the user denied it and asked for an explanation. Later, the subagent's closing message ended with an open question ("proceed?"). The parent agent, reading its context stream, answered that question affirmatively — it had no way to distinguish the subagent's fresh question from the user's earlier, still-standing denial, and treated the denied action as newly open.
- A subagent's file edit hit a permission prompt; the user denied it and typed a clarifying message. That denial-plus-message reached the parent only as plain text embedded in the subagent's report. The parent misread the user's own words as the subagent's reasoning, judged the design sound, and moved to advance the denied step — then, finding no channel to message the subagent back, attempted to complete the denied work itself, inline.
In both cases the user caught the issue before anything was committed or shipped. But the root cause is structural: the channel between subagent and parent carries no attribution metadata, and the harness carries no session-level memory of "this action was denied."
Current behavior (verified 2026-08-25)
- The parent agent receives subagent output as an ordinary tool result. There is no structured event type for "user denied
<tool>invoked by<subagent>, and said<message>" — only whatever prose the subagent's own report happens to contain. - The harness does not persist denial state across a session. Durable blocking of an action exists only through statically configured
permissions.denyrules orPreToolUsehooks set up ahead of time. Nothing in the runtime stops a parent agent from re-attempting a denied action directly, or via a newly spawned subagent — avoiding a repeat is purely a matter of model behavior, not enforcement. - Since these incidents, two relevant hardenings have shipped:
- v2.1.198+: the harness enforces that no agent-generated message can count as approval for a pending permission prompt, and no agent message can alter permission settings or configuration.
- v2.1.234 (2026-08-17): session-scoped permission answers, including denials, are no longer dropped when the answer is given in response to a background subagent's prompt.
- Additionally,
SendMessagenow allows sending a message to an already-running subagent, which removes the "no channel back, so I'll just finish the denied work myself" pressure observed in incident 2. - None of the above address the core gap: a past denial (from an earlier turn, or reported secondhand through a subagent) still has no attributed, structured representation in the parent's context. The parent's only defense today is a prompt-level instruction to itself.
Proposed behavior
- When a user answers a permission prompt that was raised on behalf of a subagent, inject a system-attributed event directly into the parent agent's transcript, independent of and in addition to whatever the subagent itself reports. Suggested shape:
user denied <tool> for <subagent-id>: "<user's typed message, verbatim>". This event should be clearly marked as system-attributed (not generated by any agent), so the parent can distinguish it from ordinary conversational text. - Optionally, let the harness track denied actions per session (tool + target + relevant argument fingerprint) and surface a warning if the same or a materially similar action is re-attempted, whether by the same subagent, a new subagent, or the parent itself.
- The goal in both cases is the same: give the parent agent a structured, trustworthy signal about who denied what, so that honoring the denial doesn't depend on the parent correctly inferring provenance from unmarked prose.
Workaround today
Prompt-level rules in project/user instructions (e.g. CLAUDE.md): a denial holds until the user explicitly lifts it; every input is attributed to its source before being acted on; subagent output is treated as a report rather than an instruction; task-level approval is never treated as step-level approval; a question attached to a denial is answered and then the turn stops. This works as far as model compliance goes, but it is an instruction the model can follow or fail to follow — it is not something the runtime enforces.
Related issues
- #27203 —
canUseToolcallback not invoked for background subagent tool calls (closed stale). Adjacent: also about permission-prompt plumbing for subagents, but about the callback firing at all, not about attribution of the resulting answer to the parent. - #85982 — subagent inheritance of parent classifier trust is unreliable. Adjacent: about trust propagating down from parent to subagent; this issue is about a signal propagating up from subagent to parent.
- #84679 — fabricated user messages / phantom attribution. Adjacent: also a provenance failure in the transcript, but about text being wrongly attributed to the user, not about the user's real denial losing attribution on the way back up.
- #77214 — similar phantom-attribution class as #84679; relevant as prior art on transcript provenance issues, but not this specific denial-relay gap.
- #80998 — dynamic system-prompt sections silently override CLAUDE.md with no precedence rule (subsumes #80988). Same root defect — no attribution signal in the concatenated prompt — at a different boundary: harness-authored text acquiring user authority, versus (here) the user's real denial losing its authority on the way up through a relay.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗