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

  1. Use a different agent type for the nested call (e.g. spawn a 'reviewer' instead of the same agent)
  2. Unset or correct the PI_BLOCKED_AGENT environment variable if it is stale
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/7dc50519bd6d8246. Report an issue: GitHub.