can1357/oh-my-pi · error

ACP session load was cancelled: ${sessionId}

Error message

ACP session load was cancelled: ${sessionId}

What it means

Thrown when opening a stored ACP session (session/load or session/resume path) and the new session's switchSession(sessionPath) returns false instead of loading the file. switchSession only returns false when a `session_before_switch` extension hook cancels the "resume" transition. The disposable session is then cleaned up and the error rethrown.

Source

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

		}
		return await this.#registerPreparedSession(session, params.mcpServers ?? [], setToolUIContext);
	}

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

	async #registerPreparedSession(
		session: AgentSession,
		mcpServers: McpServer[],
		setToolUIContext: ((uiContext: ExtensionUIContext, hasUI: boolean) => void) | undefined,
	): Promise<ManagedSessionRecord> {
		const record = this.#createManagedSessionRecord(session, setToolUIContext);
		session.setClientBridge(createAcpClientBridge(this.#connection, session.sessionId, this.#clientCapabilities));
		// `record.lifetimeUnsubscribe` is installed in `#scheduleBootstrapUpdates`
		// so it shares the bootstrap race guard — see that comment for why.
		try {

View on GitHub (pinned to 9690622007)

Solutions

  1. Find the extension registering `session_before_switch` and remove/narrow its cancel logic for reason "resume".
  2. Resume with extensions disabled to confirm the hook is the blocker, then re-enable selectively.
  3. If the veto is intentional, delete or archive the stored session file instead of resuming.

Example fix

// before
if (event.reason === "resume") return { cancel: true };
// after
if (event.reason === "resume" && blockedSessionFiles.has(event.targetSessionFile)) return { cancel: true };
return { cancel: false };
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm no resume-vetoing hooks are active before load
const vetoing = extensions.some(e => e.handles("session_before_switch") && e.cancels("resume"));

Try / catch

try {
  await agent.load({ sessionId, cwd });
} catch (err) {
  if (err.message.includes("load was cancelled")) {
    // a session_before_switch hook blocked resuming this session file
  }
  throw err;
}

Prevention

When it happens

Trigger: Loading/resuming a stored session by path where an installed extension's `session_before_switch` handler returns { cancel: true } for reason "resume" targeting that session file.

Common situations: Guard rails in extensions that block resuming specific sessions (e.g. archived, shared, or cost-locked sessions); leftover hooks from a removed workflow.

Related errors


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