can1357/oh-my-pi · error · StructuredSubagentError

Subagent isolation, apply, and merge controls are unavailabl

Error message

Subagent isolation, apply, and merge controls are unavailable in plan mode.

What it means

This error is thrown during subagent preflight when a structured subagent request includes isolation controls (requested/apply/merge on the `isolation` object) while the session is in plan mode. Plan mode is read-only by design: spawning agents that create worktrees, apply changes, or merge results would violate it. `assertPlanControlsAllowed` (via `resolveEffectiveSubagentPolicy`) rejects any isolation object that sets any of those keys before the agent is spawned.

Source

Thrown at packages/coding-agent/src/task/structured-subagent.ts:208

function createPlanModeAgent(agent: AgentDefinition): AgentDefinition {
	const tools = [...PLAN_MODE_TOOLS, ...(agent.tools ?? []).filter(tool => tool === "ast_grep")];
	return {
		...agent,
		systemPrompt: `${planModeSubagentPrompt}\n\n${agent.systemPrompt}`,
		tools,
		spawns: undefined,
		prewalk: undefined,
	};
}

function assertPlanControlsAllowed(request: StructuredSubagentRequest, planMode: boolean): void {
	if (!planMode) return;
	const isolation = request.isolation;
	if (
		isolation &&
		(Object.hasOwn(isolation, "requested") || Object.hasOwn(isolation, "apply") || Object.hasOwn(isolation, "merge"))
	) {
		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(

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove the `isolation` field (or the requested/apply/merge keys) from the subagent request while in plan mode
  2. Exit plan mode (accept the plan) before spawning an isolated subagent
  3. Guard the call site: only pass isolation controls when `session` is not in plan mode

Example fix

// before
await task({ agent: "researcher", isolation: { requested: true } });
// after
await task({ agent: "researcher" }); // plan mode: no isolation controls
Defensive patterns

Strategy: validation

Validate before calling

if (sessionPlanMode && request.isolation && ("requested" in request.isolation || "apply" in request.isolation || "merge" in request.isolation)) {
  throw new Error("Cannot use isolation controls in plan mode");
}
await task(request);

Try / catch

try {
  await task(req);
} catch (e) {
  if (e instanceof StructuredSubagentError && e.stage === "preflight" && e.message.includes("plan mode")) {
    const { isolation, ...rest } = req;
    return task(rest); // retry without isolation
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the structured subagent (task tool) with `isolation: { requested: true }` (or `apply`/`merge` set, even to false via Object.hasOwn presence check) while the parent session is in plan mode.

Common situations: A main-agent prompt template that always attaches isolation options now running under plan mode; a user toggles plan mode mid-session and re-runs a previously valid task call; programmatic SDK callers reusing a request builder that hardcodes isolation settings.

Related errors


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