can1357/oh-my-pi · error · StructuredSubagentError
Cannot spawn ${blockedAgent} agent from within itself (recur
Error message
Cannot spawn ${blockedAgent} agent from within itself (recursion prevention). Use a different agent type. What it means
Thrown in preflight to prevent an agent from spawning itself. The blocked agent name comes from the request's `blockedAgent` field or the `PI_BLOCKED_AGENT` environment variable; if it equals the requested `agentName`, `assertDepthAndSpawnAllowed` refuses the spawn.
Source
Thrown at packages/coding-agent/src/task/structured-subagent.ts:226
throw new StructuredSubagentError(
"preflight",
"Subagent isolation, apply, and merge controls are unavailable in plan mode.",
);
}
}
function assertDepthAndSpawnAllowed(request: StructuredSubagentRequest, agentName: string): void {
const taskDepth = request.session.taskDepth ?? 0;
const maxDepth = request.session.settings.get("task.maxRecursionDepth") ?? 2;
if (!canSpawnAtDepth(maxDepth, taskDepth)) {
throw new StructuredSubagentError(
"preflight",
`Cannot spawn another agent at task depth ${taskDepth}; maximum depth is ${maxDepth}.`,
);
}
const blockedAgent = request.blockedAgent ?? $env.PI_BLOCKED_AGENT;
if (blockedAgent && blockedAgent === agentName) {
throw new StructuredSubagentError(
"preflight",
`Cannot spawn ${blockedAgent} agent from within itself (recursion prevention). Use a different agent type.`,
);
}
const spawnPolicy = resolveSpawnPolicy(request.session.getSessionSpawns());
if (!spawnPolicy.enabled || (spawnPolicy.allowedAgents !== null && !spawnPolicy.allowedAgents.includes(agentName))) {
throw new StructuredSubagentError(
"preflight",
`Cannot spawn '${agentName}'. Allowed: ${spawnPolicy.allowedErrorText}`,
);
}
}
/**
* Resolve every policy shared by task and eval before allocating artifacts or
* dispatching work. Callers translate {@link StructuredSubagentError} into
* their own wire-level error surface.
*/View on GitHub (pinned to 9690622007)
Solutions
- Use a different agent type for the nested call (e.g. spawn a 'reviewer' instead of the same agent)
- Unset or correct the PI_BLOCKED_AGENT environment variable if it is stale
- Restructure the workflow so the agent's sub-steps are handled by distinct agents
Example fix
// before (inside implementer agent)
await task({ agent: "implementer", prompt });
// after
await task({ agent: "reviewer", prompt }); Defensive patterns
Strategy: validation
Validate before calling
const blocked = request.blockedAgent ?? process.env.PI_BLOCKED_AGENT;
if (blocked && blocked === agentName) {
throw new Error(`${agentName} cannot spawn itself; pick another agent`);
}
await task({ agent: agentName, blockedAgent: blocked }); Try / catch
try {
await task(req);
} catch (e) {
if (e instanceof StructuredSubagentError && e.message.includes("recursion prevention")) {
return task({ ...req, agent: alternateAgentName });
}
throw e;
} Prevention
- Never put an agent's own name in its own prompt's task calls
- Set PI_BLOCKED_AGENT deliberately and document it in nested-agent templates
- Maintain an agent-to-delegate mapping for self-spawn situations
When it happens
Trigger: A subagent whose name is recorded as blocked (or running under PI_BLOCKED_AGENT=that name) calls the task tool with its own agent name; e.g. an 'implementer' agent trying to spawn 'implementer'.
Common situations: Recursive prompts that re-invoke the same agent definition; copying an agent's task call into that agent's own prompt; PI_BLOCKED_AGENT left set in the environment from a previous nesting level.
Related errors
- Subagent isolation, apply, and merge controls are unavailabl
- Cannot spawn another agent at task depth ${taskDepth}; maxim
- Unknown agent "${agentName}". Available: ${available}
- Invalid ${scope} output schema: ${error}
- agent() blocked: turn token budget exhausted (${turnBudget.s
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/7dc50519bd6d8246.
Report an issue: GitHub.