{"record":{"id":"8fa5d4aec44f2e81","repo":"can1357/oh-my-pi","slug":"cannot-complete-a-dropped-goal","errorCode":null,"errorMessage":"cannot complete a dropped goal","messagePattern":"cannot complete a dropped goal","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/goals/runtime.ts","lineNumber":484,"sourceCode":"\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}\n\n\tbuildActivePrompt(): string | undefined {\n\t\tconst state = this.#host.getState();\n\t\treturn state?.enabled && state.goal && state.goal.status === \"active\"\n\t\t\t? renderGoalPrompt(\"active\", state.goal)\n\t\t\t: undefined;","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/goals/runtime.ts#L466-L502","documentation":"completeGoalFromTool refuses to complete a goal whose status is 'dropped'. Dropping is itself a terminal state meaning the goal was abandoned; marking an abandoned goal complete would falsify the outcome record.","triggerScenarios":"Calling the completion tool after the goal was dropped via the drop op; flows that drop-then-complete; a stale agent turn holding a pre-drop reference that still calls complete.","commonSituations":"User drops a goal mid-run and the agent then tries to finish it; scripts replaying queued ops without re-reading state; confusion between drop (abandon) and complete (achieve).","solutions":["Use the drop op as the terminal action — do not call complete afterwards","Check state.goal.status before completing and skip when 'dropped'","Cancel pending completion tool calls once a drop is issued"],"exampleFix":"// before\nawait dropGoal();\nawait runtime.completeGoalFromTool(); // throws: dropped\n// after\nawait dropGoal();\nconst state = host.getState();\nif (state?.goal?.status === 'active' || state?.goal?.status === 'budget-limited') {\n  await runtime.completeGoalFromTool();\n}","handlingStrategy":"validation","validationCode":"const state = host.getState();\nif (state?.goal?.status === 'dropped') throw new Error('goal was dropped; cannot complete');","typeGuard":"function isCompletable(state: { goal?: { status: string } } | undefined | null): boolean {\n  return !!state?.goal && !['complete', 'dropped'].includes(state.goal.status);\n}","tryCatchPattern":"try {\n  await runtime.completeGoalFromTool();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('dropped goal')) {\n    return { ok: false, reason: 'goal-dropped' }; // cancellation already decided\n  }\n  throw err;\n}","preventionTips":["Once dropped, cancel any queued completion ops in your flow","Re-read state after drop before issuing further goal mutations","Keep drop and complete as mutually exclusive terminal actions in the UI/agent logic"],"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"}