{"record":{"id":"51b3ef06f2269256","repo":"can1357/oh-my-pi","slug":"cannot-complete-goal-because-no-goal-is-active","errorCode":null,"errorMessage":"cannot complete goal because no goal is active","messagePattern":"cannot complete goal because no goal is active","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/goals/runtime.ts","lineNumber":478,"sourceCode":"\t\t\tconst dropped = { ...state.goal, status: \"dropped\" as const, updatedAt: this.#now() };\n\t\t\tthis.#clearActiveAccounting();\n\t\t\tthis.#budgetReportedFor = undefined;\n\t\t\tawait this.#host.emit({\n\t\t\t\ttype: \"goal_updated\",\n\t\t\t\tgoal: dropped,\n\t\t\t\tstate: { ...state, enabled: false, goal: dropped },\n\t\t\t});\n\t\t\tawait this.#commitState(undefined, { persist: \"none\", emit: false });\n\t\t\treturn dropped;\n\t\t});\n\t}\n\n\tasync completeGoalFromTool(): Promise<Goal> {\n\t\treturn await this.#withAccounting(async () => {\n\t\t\tawait this.#flushUsageLocked(\"suppressed\");\n\t\t\tconst state = this.#getStateClone();\n\t\t\tif (!state?.goal) {\n\t\t\t\tthrow new Error(\"cannot complete goal because no goal is active\");\n\t\t\t}\n\t\t\tif (state.goal.status === \"complete\") {\n\t\t\t\tthrow new Error(\"goal is already complete\");\n\t\t\t}\n\t\t\tif (state.goal.status === \"dropped\") {\n\t\t\t\tthrow new Error(\"cannot complete a dropped goal\");\n\t\t\t}\n\t\t\tstate.enabled = false;\n\t\t\tstate.goal.status = \"complete\";\n\t\t\tstate.goal.updatedAt = this.#now();\n\t\t\tstate.mode = \"exiting\";\n\t\t\tstate.reason = \"completed\";\n\t\t\tthis.#clearActiveAccounting();\n\t\t\tthis.#budgetReportedFor = undefined;\n\t\t\tawait this.#commitState(state, { persist: \"goal\" });\n\t\t\treturn state.goal;\n\t\t});\n\t}","sourceCodeStart":460,"sourceCodeEnd":496,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/goals/runtime.ts#L460-L496","documentation":"completeGoalFromTool finalizes the session's active goal, so it throws when no goal exists in the state — there is nothing to mark complete. It is the tool-invoked completion path and requires a live GoalModeState with a goal.","triggerScenarios":"Invoking the goal-complete tool on a session with no goal created; calling it after the goal was dropped (dropped has its own message) or after state was cleared by a restart; double-invocation after state reset.","commonSituations":"LLM emitting the completion tool without the goal op having run; session restored without goal persistence; flows that complete the goal then call the tool again in a new context.","solutions":["Ensure a goal exists (createGoal) before attempting completion","Check getState()?.goal before emitting the complete op","Fix the agent flow so the completion tool is only exposed while a goal is active"],"exampleFix":"// before\nawait runtime.completeGoalFromTool(); // throws if none\n// after\nconst state = host.getState();\nif (!state?.goal) throw new Error('No goal to complete');\nawait runtime.completeGoalFromTool();","handlingStrategy":"validation","validationCode":"const state = host.getState();\nif (!state?.goal) throw new Error('completeGoalFromTool requires an existing goal');","typeGuard":"function hasGoal(state: { goal?: unknown } | undefined | null): boolean {\n  return !!state?.goal;\n}","tryCatchPattern":"try {\n  await runtime.completeGoalFromTool();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('no goal is active')) {\n    return { ok: false, reason: 'no-goal' }; // surface as no-op to the agent\n  }\n  throw err;\n}","preventionTips":["Only expose the goal-complete tool when a goal is active in the session","Check state before emitting tool calls from agent logic","Ensure goal state is persisted/restored across session boundaries"],"tags":["state","goals","tool"],"backgroundTag":"invalid-state-transition","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}