MoonshotAI / MoonshotAI/kimi-code

Permission-denied tool calls trigger unbounded subagent spawning (no depth guard, custom profiles get Agent tool by default)

Open
#3,128 0 comments 0 reactions 0 assignees View on GitHub

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

  1. 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).
  2. Instead of stopping, the main agent spawns a subagent (Agent tool) to attempt the same call.
  3. 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:

  1. 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.ts only records parentAgentId). Skills have a nesting limit (agent/tools/skill/skill.ts:17); subagents don't.
  2. Custom subagent profiles get the Agent tool by default. A file-loaded profile without an explicit tools field passes tools === undefined through (workspaceAgentProfileLoader/internal/agentProfileFromFile.ts:24), and isToolActive (agent/toolPolicy/evaluate.ts:15-23) then skips the allowlist check → the custom subagent can itself spawn subagents. Additionally, declaring subagents: ['*'] 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 exclude Agent, so the built-in chain caps at 2 levels.
  3. 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 note Agent/AgentSwarm are 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

  1. Hard depth limit (root fix): compute nesting depth along the parentAgentId chain in SubagentTool.launch() and return an error tool result past a small threshold (e.g. 3); and/or cap total live subagents.
  2. Tighten defaults: for file-loaded custom profiles, default to excluding Agent/AgentSwarm when tools is unspecified (opt-in delegation), matching the least-privilege style of built-in profiles.
  3. Prompt fix: extend the denial guidance to explicitly cover "or by delegating it to a subagent", and apply it to the main agent too.
  4. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.