{"record":{"id":"103229a90807ad9c","repo":"can1357/oh-my-pi","slug":"acp-session-fork-was-cancelled-params-sessionid","errorCode":null,"errorMessage":"ACP session fork was cancelled: ${params.sessionId}","messagePattern":"ACP session fork was cancelled: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/modes/acp/acp-agent.ts","lineNumber":1283,"sourceCode":"\n\t\tconst storedSession = await this.#findStoredSession(sessionId, cwd);\n\t\tif (!storedSession) {\n\t\t\tthrow new Error(`ACP session not found: ${sessionId}`);\n\t\t}\n\t\treturn await this.#openStoredSession(storedSession.path, cwd, mcpServers, sessionId);\n\t}\n\n\tasync #forkManagedSession(params: ForkSessionRequest): Promise<ManagedSessionRecord> {\n\t\tconst sourcePath = await this.#resolveForkSourceSessionPath(params.sessionId);\n\t\tconst { session, setToolUIContext } = normalizeCreatedAcpSession(\n\t\t\tawait this.#createSession(path.resolve(params.cwd), {\n\t\t\t\tinteractivePrompts: this.#clientCapabilities?.elicitation?.form != null,\n\t\t\t}),\n\t\t);\n\t\ttry {\n\t\t\tconst success = await session.switchSession(sourcePath);\n\t\t\tif (!success) {\n\t\t\t\tthrow new Error(`ACP session fork was cancelled: ${params.sessionId}`);\n\t\t\t}\n\t\t\tconst forked = await session.fork();\n\t\t\tif (!forked) {\n\t\t\t\tthrow new Error(`ACP session fork failed: ${params.sessionId}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tawait this.#disposeStandaloneSession(session);\n\t\t\tthrow error;\n\t\t}\n\t\treturn await this.#registerPreparedSession(session, params.mcpServers ?? [], setToolUIContext);\n\t}\n\n\tasync #openStoredSession(\n\t\tsessionPath: string,\n\t\tcwd: string,\n\t\tmcpServers: McpServer[],\n\t\tsessionId: string,\n\t): Promise<ManagedSessionRecord> {","sourceCodeStart":1265,"sourceCodeEnd":1301,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/modes/acp/acp-agent.ts#L1265-L1301","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect installed extensions/plugins for `session_before_switch` handlers returning cancel:true and remove or narrow the handler (e.g. only cancel unrelated reasons).","Retry the fork with extensions disabled to confirm the hook is the cause.","If the hook is intentional, fork outside the guarded scope or expose a hook option that allows fork/source-adopt transitions."],"exampleFix":"// extension: before\nasync function onSessionBeforeSwitch() {\n  return { cancel: true };\n}\n// after\nasync function onSessionBeforeSwitch(event) {\n  if (event.reason === \"switch\") return { cancel: true }; // allow fork/resume transitions\n  return { cancel: false };\n}","handlingStrategy":"try-catch","validationCode":"// no pre-call check: hook cancellation is decided server-side; ensure no extension vetoes before forking\nconst hooksActive = extensions.some(e => e.handles(\"session_before_switch\"));","typeGuard":null,"tryCatchPattern":"try {\n  await agent.fork({ sessionId });\n} catch (err) {\n  if (err.message.includes(\"fork was cancelled\")) {\n    // a session_before_switch hook vetoed the fork; disable/narrow the hook and retry\n  }\n  throw err;\n}","preventionTips":["Audit extensions for session_before_switch cancel logic before enabling fork workflows.","Keep hook cancel conditions narrow (specific reasons/sessions).","Test fork flows with the full extension set enabled."],"tags":["acp","session-fork","cancelled-by-hook"],"backgroundTag":"operation-cancelled-by-hook","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}