can1357/oh-my-pi · error

ACP session fork was cancelled: ${params.sessionId}

Error message

ACP session fork was cancelled: ${params.sessionId}

What it means

Thrown by AcpAgent's fork handling (session/fork) when a freshly created session's switchSession() to the source session file returns false instead of loading it. AgentSession.switchSession() returns false only when a `session_before_switch` extension hook cancels the transition (reason "resume"). The fork is aborted and the throwaway session is disposed.

Source

Thrown at packages/coding-agent/src/modes/acp/acp-agent.ts:1283

		const storedSession = await this.#findStoredSession(sessionId, cwd);
		if (!storedSession) {
			throw new Error(`ACP session not found: ${sessionId}`);
		}
		return await this.#openStoredSession(storedSession.path, cwd, mcpServers, sessionId);
	}

	async #forkManagedSession(params: ForkSessionRequest): Promise<ManagedSessionRecord> {
		const sourcePath = await this.#resolveForkSourceSessionPath(params.sessionId);
		const { session, setToolUIContext } = normalizeCreatedAcpSession(
			await this.#createSession(path.resolve(params.cwd), {
				interactivePrompts: this.#clientCapabilities?.elicitation?.form != null,
			}),
		);
		try {
			const success = await session.switchSession(sourcePath);
			if (!success) {
				throw new Error(`ACP session fork was cancelled: ${params.sessionId}`);
			}
			const forked = await session.fork();
			if (!forked) {
				throw new Error(`ACP session fork failed: ${params.sessionId}`);
			}
		} catch (error) {
			await this.#disposeStandaloneSession(session);
			throw error;
		}
		return await this.#registerPreparedSession(session, params.mcpServers ?? [], setToolUIContext);
	}

	async #openStoredSession(
		sessionPath: string,
		cwd: string,
		mcpServers: McpServer[],
		sessionId: string,
	): Promise<ManagedSessionRecord> {

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect installed extensions/plugins for `session_before_switch` handlers returning cancel:true and remove or narrow the handler (e.g. only cancel unrelated reasons).
  2. Retry the fork with extensions disabled to confirm the hook is the cause.
  3. If the hook is intentional, fork outside the guarded scope or expose a hook option that allows fork/source-adopt transitions.

Example fix

// extension: before
async function onSessionBeforeSwitch() {
  return { cancel: true };
}
// after
async function onSessionBeforeSwitch(event) {
  if (event.reason === "switch") return { cancel: true }; // allow fork/resume transitions
  return { cancel: false };
}
Defensive patterns

Strategy: try-catch

Validate before calling

// no pre-call check: hook cancellation is decided server-side; ensure no extension vetoes before forking
const hooksActive = extensions.some(e => e.handles("session_before_switch"));

Try / catch

try {
  await agent.fork({ sessionId });
} catch (err) {
  if (err.message.includes("fork was cancelled")) {
    // a session_before_switch hook vetoed the fork; disable/narrow the hook and retry
  }
  throw err;
}

Prevention

When it happens

Trigger: An ACP client sends a fork request whose source session resolves to a valid session file, but an extension/plugin registered a `session_before_switch` handler that returns { cancel: true } when the new session tries to adopt that file.

Common situations: A user has an extension hook that blocks session switches or forks (e.g. to protect the active session); a hook was written with overly broad matching (fires for reason "resume") during automated ACP clients.

Related errors


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