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
- Find the extension registering `session_before_switch` and remove/narrow its cancel logic for reason "resume".
- Resume with extensions disabled to confirm the hook is the blocker, then re-enable selectively.
- 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
- Review session_before_switch hooks when load/resume starts failing.
- Scope hook vetoes to explicit session files, not all resumes.
- Keep a mode to load sessions with extensions disabled for diagnosis.
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
- ACP session fork was cancelled: ${params.sessionId}
- ACP session fork failed: ${params.sessionId}
- Trusted extension failed to load: ${trustedExtensions.errors
- Unsupported ACP session: ${sessionId}
- ACP session ${session.sessionId} is already loaded for ${act
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/cebffe7f78046cc9.
Report an issue: GitHub.