{"record":{"id":"48d8a09b674bcc1f","repo":"can1357/oh-my-pi","slug":"cannot-replace-goal-because-no-goal-is-active","errorCode":null,"errorMessage":"cannot replace goal because no goal is active","messagePattern":"cannot replace goal because no goal is active","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/goals/runtime.ts","lineNumber":408,"sourceCode":"\t\t\tif (existing?.goal && existing.goal.status !== \"dropped\" && existing.goal.status !== \"complete\") {\n\t\t\t\tthrow new Error(\"cannot create a new goal because this session already has a goal\");\n\t\t\t}\n\t\t\tconst state = this.#createGoalState(objective, input.tokenBudget);\n\t\t\tthis.#budgetReportedFor = undefined;\n\t\t\tthis.#markActiveAccounting(state.goal);\n\t\t\tawait this.#commitState(state, { persist: \"goal\" });\n\t\t\treturn state;\n\t\t});\n\t}\n\n\tasync replaceGoal(input: { objective: string; tokenBudget?: number }): Promise<GoalModeState> {\n\t\tconst objective = input.objective.trim();\n\t\tif (!objective) throw new Error(\"objective is required when op=replace\");\n\t\tvalidateTokenBudget(input.tokenBudget);\n\t\treturn await this.#withAccounting(async () => {\n\t\t\tconst existing = this.#host.getState();\n\t\t\tif (!existing?.enabled || !isAccountingStatus(existing.goal)) {\n\t\t\t\tthrow new Error(\"cannot replace goal because no goal is active\");\n\t\t\t}\n\t\t\tawait this.#flushUsageLocked(\"suppressed\");\n\t\t\tconst state = this.#createGoalState(objective, input.tokenBudget);\n\t\t\tthis.#budgetReportedFor = undefined;\n\t\t\tthis.#markActiveAccounting(state.goal);\n\t\t\tawait this.#commitState(state, { persist: \"goal\" });\n\t\t\treturn state;\n\t\t});\n\t}\n\n\tasync resumeGoal(): Promise<GoalModeState> {\n\t\treturn await this.#withAccounting(async () => {\n\t\t\tconst state = this.#getStateClone();\n\t\t\tif (!state?.goal) throw new Error(\"No paused goal.\");\n\t\t\tif (state.goal.status === \"complete\") throw new Error(\"Goal is already complete.\");\n\t\t\tstate.enabled = true;\n\t\t\tstate.mode = \"active\";\n\t\t\tstate.reason = undefined;","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/goals/runtime.ts#L390-L426","documentation":"replaceGoal only works when goal mode is enabled and the existing goal is in an accounting status ('active' or 'budget-limited'). It throws when there is no goal at all, goal mode is disabled, or the goal is already complete/dropped.","triggerScenarios":"Calling replaceGoal on a fresh session with no goal; replacing after the goal was dropped or completed; goal mode paused/disabled via the pause path; resuming with replace instead of resumeGoal.","commonSituations":"Blind retry logic that calls replace without checking state; a session restart where the goal never persisted; confusion between resume (paused goal) and replace (live goal) ops.","solutions":["Call createGoal instead if no goal is active","Call resumeGoal instead if the goal is merely paused","Check that getState().enabled is true and goal.status is 'active' or 'budget-limited' before replacing","Fix flow logic so replace is only offered while a goal is live"],"exampleFix":"// before\nawait runtime.replaceGoal({ objective }); // throws when none active\n// after\nconst state = host.getState();\nif (state?.enabled && (state.goal.status === 'active' || state.goal.status === 'budget-limited')) {\n  await runtime.replaceGoal({ objective });\n} else {\n  await runtime.createGoal({ objective });\n}","handlingStrategy":"type-guard","validationCode":"const state = host.getState();\nconst active = state?.enabled && (state.goal.status === 'active' || state.goal.status === 'budget-limited');\nif (!active) throw new Error('replaceGoal requires an active goal');","typeGuard":"function hasActiveGoal(state: { enabled: boolean; goal: { status: string } } | undefined | null): boolean {\n  return !!state?.enabled && (state.goal.status === 'active' || state.goal.status === 'budget-limited');\n}","tryCatchPattern":"try {\n  await runtime.replaceGoal({ objective });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('no goal is active')) {\n    await runtime.createGoal({ objective });\n  } else throw err;\n}","preventionTips":["Gate the replace action on getState(): enabled && status in {active, budget-limited}","Map user intent correctly: no goal → create, paused → resume, live → replace","Persist and restore goal state so restarts do not silently drop active goals"],"tags":["state","goals","conflict"],"backgroundTag":"invalid-state-transition","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}