fix(subagent): children inherit the parent's permission policy and thinking level - #172
Closed
jasonkneen wants to merge 1 commit into
Closed
jasonkneen wants to merge 1 commit into
jasonkneen wants to merge 1 commit into
Conversation
…inking level Invariant: a subagent child inherits model, provider, reasoning effort and permission policy unless explicitly overridden, and is never more permissive than the parent. Cause: createSubagentRpcSession dropped --thinking whenever the agent file set a model, and the permission policy reached the child only through the inherited process.env. The child re-resolved its own policy from STEP_* env and settings, so a preset the parent chose at runtime (/permissions, CLI flags, trusted project) never reached it. That let an Ask or Read Only parent spawn a Bypass child. Fix: buildSubagentChildArgs passes the parent's thinking level unless the model declares one with a :<level> suffix. It also passes the parent's effective policy as --approval-mode, --non-interactive-approval and --tool-override, which outrank env and persisted settings in the child; STEP_AUTO_RESUME is pinned in the env. resolveStepChildPermissionPolicy applies the parent's headless downgrade and never gives a defaulted parent's child an explicit unattended allow (auto + deny). The subagent tool reads the live state over the extension event bus when each child spawns. A keep-alive child whose spawn-time policy no longer matches is replaced on the next reply.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Critical keep-alive and policy-transition issues remain unresolved, along with cleanup and model-resolution concerns.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 2
Open (2)
What changed in this PR
Updates Step subagents to inherit the parent’s model, thinking level, and permission policy.
Changes:
- Propagates permission, thinking, model, and tool settings.
- Adds live policy propagation and policy-aware keep-alive replacement.
- Adds inheritance, wiring, and lifecycle tests.
| File | Summary |
|---|---|
packages/coding-agent/test/subagent-inherit-settings.test.ts |
Tests inheritance, wiring, and keep-alive behavior. |
packages/coding-agent/src/step/permissions.ts |
Resolves child permission policies and state exchange. |
packages/coding-agent/src/features/subagent/rpc-adapter.ts |
Builds inherited child arguments and environment. |
packages/coding-agent/src/features/subagent/execute.ts |
Supplies live parent state to child runs. |
packages/coding-agent/src/features/step.ts |
Registers permission-state event handling. |
packages/coding-agent/src/features/step-subagent.ts |
Manages policy-aware child replacement. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+415
to
+416
| if (live.isTurnActive() || live.permissionKey === subagentPermissionKey(input.permission)) { | ||
| return live.runTurn(input); |
| // A live child keeps the policy it was spawned with. The parent's changed | ||
| // since (e.g. tightened via /permissions), so replace the idle child and | ||
| // resume its transcript under the current policy. | ||
| live.stop(); |
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.

Problem
Invariant: a subagent child inherits model, provider, reasoning effort and permission policy unless something explicitly overrides them, and is never more permissive than the parent.
packages/coding-agent/src/features/subagent/rpc-adapter.ts:192):if (input.thinkingLevel && !input.agent.model) args.push("--thinking", ...). Any agent file that setmodel:lost the parent's thinking level, even when it declared none.rpc-adapter.ts:170-181,buildSubagentChildEnv): the child only saw...process.env, so it re-resolved its own policy from env and settings (step/permissions.ts:211-260). A preset the parent picked at runtime (/permissions, Shift+Tab cycle), CLI flags (--approval-mode,--non-interactive-approval,--tool-override), and trusted-project policy never reached it. A parent in Ask or Read Only could spawn a child that resolved to Bypass, for example through an inheritedSTEP_PERMISSION_PRESET=bypass/STEP_AUTOPILOT=1or the defaulted fallback.Fix
buildSubagentChildArgs(a new pure function pulled out ofcreateSubagentRpcSession) now:--thinking <parent>unless the model pattern declares its own level with a:<level>suffix. That suffix is the only way an agent file can declare thinking today, and an explicit--thinkingwould override it in the child. A model without reasoning support clamps the level (sdk.ts:274) and does not error.--approval-mode,--non-interactive-approvaland--tool-override tool=mode. These flags outrank everySTEP_*env var and the persisted preset in the child's resolver. Auto-resume has no flag, sobuildSubagentChildEnvpinsSTEP_AUTO_RESUME=0|1and clears any inheritedSTEP_AUTOPILOT.resolveStepChildPermissionPolicy(state, parentHasUI)instep/permissions.tsworks out what the child gets:strictandconfirmpass through unchanged. The rpc child's confirm dialogs are auto-cancelled (rpc-adapter.tsonUiRequest), so a childconfirmblocks the call and never allows it.autodrops toconfirm/deny, the same downgradeStepPermissionController.effectiveStateapplies.--non-interactive-approval deny. It is never handed an explicit unattendedallow. For an interactive defaulted parent, the child runsautoinside rpc (whose UI channel is present), so it behaves like the parent and like today. This keeps subagent writes working on a fresh install. For a headless defaulted parent, the child getsconfirm/deny.pi.events). The Step extension answersstep:permission-state-requestwithpermissions.getState(), andexecuteSubagentreads it when each child spawns, so a preset switched mid-session applies to the next child. With no Step extension loaded, no flags are sent, which matches the old behaviour.permissionKey). If a reply arrives after the parent's policy has changed, the idle child is replaced and its transcript is resumed under the current policy, so it cannot keep running with a policy looser than the parent's.ctx.model, it is already qualified as${provider}/${id}(execute.ts). A new wiring test now covers this.Test
New file:
packages/coding-agent/test/subagent-inherit-settings.test.ts. The policy cases build the child's argv and env, then feed them through the child's ownparseArgsandresolveInitialStepPermissionState. The inherited env is deliberately hostile (STEP_PERMISSION_PRESET=bypass,STEP_AUTOPILOT=1).strict/read-onlyconfirm/deny(write_filedecidesconfirm, which gets auto-cancelled)auto+denyauto+deny→ childconfirmmodelwithout a thinking suffix → parent's--thinking;model: x:low→ no--thinking;llama3:8bis not treated as a declarationsubagent-child-env.test.ts)Before the fix, 8 of 13 tests failed. Examples:
parent read-only -> child read-only:AssertionError: expected 'auto' to be 'strict'parent ask -> ...:expected 'auto' to be 'confirm'defaulted interactive parent ...:expected 'allow' to be 'deny'agent file sets a model but no thinking ...:expected undefined to be 'high'Risk / behaviour change
auto+deny, not the stricterconfirm.confirmwould block every subagent write on a fresh install. Switching to it is a one-line change inresolveStepChildPermissionPolicy.onChildRespawnfires for this.features/workflow/agent-runner.ts) also callrunStepSubagentProcesswithout apermission. They keep the old behaviour, with their own path ACL.Merge note: interaction between #168 (session writer lock) and #172 (subagent inherit settings)
When both
fix/harness-session-writer-lockandfix/harness-subagent-inherit-settingsare merged, replacing an idle keep-alive subagent lane (on a parent permission change) resumes its transcript right after the old child is told to stop. The old child may still hold the session writer lock, so the replacement can fail with "session is already open". Whichever merges second should await the old child's exit before spawning the replacement.https://claude.ai/code/session_01GUdnnHEaDThHUATSwXBpV9