{"record":{"id":"32079a1be089c4a4","repo":"can1357/oh-my-pi","slug":"plan-mode-is-not-active","errorCode":null,"errorMessage":"Plan mode is not active.","messagePattern":"Plan mode is not active\\.","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/modes/acp/acp-agent.ts","lineNumber":1884,"sourceCode":"\t}\n\n\t/**\n\t * Plan-proposal handler installed while ACP plan mode is active. The agent\n\t * submits the finalized plan by writing its `<slug>`/title to\n\t * `xd://propose`; this handler validates the plan file, normalizes the\n\t * title, asks the ACP client to confirm (via `unstable_createElicitation`\n\t * when supported), and on approval keeps the chosen plan path, exits plan\n\t * mode, and notifies the client so the agent regains full tools.\n\t *\n\t * Mirrors `InteractiveMode.#handlePlanProposal` for the parts the agent sees\n\t * (same `PlanApprovalDetails` shape). Clients without form-mode elicitation\n\t * get an auto-approve so plan mode is never stranded — the agent always has\n\t * a way out.\n\t */\n\tasync #handleAcpPlanProposal(session: AgentSession, title: string): Promise<AgentToolResult<unknown>> {\n\t\tconst state = session.getPlanModeState();\n\t\tif (!state?.enabled) {\n\t\t\tthrow new ToolError(\"Plan mode is not active.\");\n\t\t}\n\t\tconst {\n\t\t\tplanFilePath,\n\t\t\tplanContent,\n\t\t\ttitle: resolvedTitle,\n\t\t} = await resolveApprovedPlan({\n\t\t\tsuppliedTitle: title,\n\t\t\tstatePlanFilePath: state.planFilePath,\n\t\t\treadPlan: url => this.#readAcpPlanFile(session, url),\n\t\t\tlistPlanFiles: () => this.#listAcpLocalPlanFiles(session),\n\t\t});\n\t\tconst approved = await this.#requestAcpPlanApprovalChoice(session.sessionId, resolvedTitle, planContent);\n\t\tconst details: PlanApprovalDetails = {\n\t\t\tplanFilePath,\n\t\t\ttitle: resolvedTitle,\n\t\t\tplanExists: true,\n\t\t};\n\t\tif (!approved) {","sourceCodeStart":1866,"sourceCodeEnd":1902,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/modes/acp/acp-agent.ts#L1866-L1902","documentation":"#handleAcpPlanProposal implements the plan-proposal tool used while ACP plan mode is active. It first checks session.getPlanModeState()?.enabled; if plan mode is not enabled it throws this ToolError. This guards the workflow contract: plan proposals are only meaningful (and only get auto-approval routing) when the session is actually in plan mode.","triggerScenarios":"Invoking the plan-proposal tool (or the agent reaching #handleAcpPlanProposal) while session.getPlanModeState() is null or { enabled: false } — e.g. the model calls the plan tool after plan mode was exited or never entered.","commonSituations":"A session was switched out of plan mode mid-run but the model still attempts the plan tool; a caller resumes a session whose plan-mode state was not persisted; the ACP client enabled plan mode on a different session object than the one running.","solutions":["Enable plan mode on the session (setPlanModeState({ enabled: true, ... }) or an ACP mode change to ACP_PLAN_MODE_ID) before proposing a plan.","Verify the session instance is the same one whose plan mode was enabled.","Have the model/agent flow check plan-mode state before invoking the plan-proposal tool."],"exampleFix":"// before\nawait proposePlan(session, title);\n// after\nsession.setPlanModeState({ enabled: true, planFilePath: DEFAULT_PLAN_FILE_URL });\nawait proposePlan(session, title);","handlingStrategy":"try-catch","validationCode":"const state = session.getPlanModeState();\nif (!state?.enabled) {\n  session.setPlanModeState({ enabled: true, planFilePath: DEFAULT_PLAN_FILE_URL });\n}\nawait proposePlan(session, title);","typeGuard":"function planModeActive(session: AgentSession): boolean {\n  return session.getPlanModeState()?.enabled === true;\n}","tryCatchPattern":"try {\n  await proposePlan(session, title);\n} catch (err) {\n  if (err instanceof ToolError && err.message === \"Plan mode is not active.\") {\n    session.setPlanModeState({ enabled: true, planFilePath: DEFAULT_PLAN_FILE_URL });\n    await proposePlan(session, title);\n  } else throw err;\n}","preventionTips":["Check plan-mode state before invoking plan-proposal tooling.","Keep the enabled session and the session running the agent the same object.","Persist/restore plan-mode state when resuming sessions."],"tags":["acp","plan-mode","state"],"backgroundTag":"invalid-state-transition","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}