MoonshotAI / MoonshotAI/kimi-code
Permission-denied tool calls trigger unbounded subagent spawning (no depth guard, custom profiles get Agent tool by default)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 7.5k
- Forks
- 1.2k
- Avg merge
- 11h 53m
- Merged PRs (30d)
- 350
Description
Summary
When a tool call is denied by permissions (e.g. an MCP tool the user has no approval for), the agent tries to work around the denial by spawning a subagent to make the same call. The subagent hits the same denial and — lacking any depth/recursion guard — can spawn sub-sub-agents, producing an unbounded tree of subagents all retrying the same denied call.
Reported internally with a screenshot showing the recursive subagent cascade; filing here so the framework-level gaps are tracked.
Observed behavior
- Main agent calls an MCP tool → permission denial (
Tool "mcp__xxx__yyy" was denied by permission rule./...was not run because the user rejected the approval request.,isError: true). - Instead of stopping, the main agent spawns a subagent (
Agenttool) to attempt the same call. - The subagent also gets denied, and either spawns another subagent (possible with custom profiles, see below) or reports back, after which the main agent spawns yet another subagent — the tree grows without limit.
Root cause analysis (agent-core-v2)
Three framework facts combine with model behavior to make this possible:
- No subagent depth or count guard. Nothing in
packages/agent-core-v2/src/agent/tools/agent/agentTool.ts(SubagentTool.launch(), L253-382) limits nesting depth or total live subagents — there is no depth tracking at all (session/agentLifecycle/subagentMetadata.tsonly recordsparentAgentId). Skills have a nesting limit (agent/tools/skill/skill.ts:17); subagents don't. - Custom subagent profiles get the
Agenttool by default. A file-loaded profile without an explicittoolsfield passestools === undefinedthrough (workspaceAgentProfileLoader/internal/agentProfileFromFile.ts:24), andisToolActive(agent/toolPolicy/evaluate.ts:15-23) then skips the allowlist check → the custom subagent can itself spawn subagents. Additionally, declaringsubagents: ['*']disables the allowlist entirely (agentTool.ts:185-199,profile-shared.ts:34-35), allowing a profile to spawn itself. Built-in profiles (coder/explore/plan) correctly excludeAgent, so the built-in chain caps at 2 levels. - Prompts don't forbid delegation-as-bypass. The closest existing guidance is
app/agentProfileCatalog/system.md:25("do not route around the denial by doing the same thing through a different tool or shell command") which doesn't mention subagents, and the "don't attempt to bypass the restriction" suffix is only appended for non-main agents (toolApprovalService.ts:241-246). Also noteAgent/AgentSwarmare in the default-approve list (policies/default-tool-approve.ts:8-33), so spawning a subagent never itself requires approval.
Contributing factor: permission state is per-agent — session approval/deny history is not inherited by subagents, so each child re-asks and re-fails on the same tool, feeding the retry loop.
Suggested fixes
- Hard depth limit (root fix): compute nesting depth along the
parentAgentIdchain inSubagentTool.launch()and return an error tool result past a small threshold (e.g. 3); and/or cap total live subagents. - Tighten defaults: for file-loaded custom profiles, default to excluding
Agent/AgentSwarmwhentoolsis unspecified (opt-in delegation), matching the least-privilege style of built-in profiles. - Prompt fix: extend the denial guidance to explicitly cover "or by delegating it to a subagent", and apply it to the main agent too.
- Propagate denial context: pass session-level deny/approval records (at least denials) to subagents so they don't re-attempt the same denied call.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/agent-core-v2/src/agent/tools/agent/agentTool.ts, especially SubagentTool.launch(), and trace parentAgentId handling through session/agentLifecycle/subagentMetadata.ts. Then read workspaceAgentProfileLoader/internal/agentProfileFromFile.ts, agent/toolPolicy/evaluate.ts, and the denial guidance in app/agentProfileCatalog/system.md and toolApprovalService.ts. Done means the selected framework safeguards prevent unbounded retries and custom profiles cannot bypass the intended delegation limits.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- ai, cli
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100