can1357/oh-my-pi · error · Error
cannot complete a dropped goal
Error message
cannot complete a dropped goal
What it means
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.
Source
Thrown at packages/coding-agent/src/goals/runtime.ts:484
state: { ...state, enabled: false, goal: dropped },
});
await this.#commitState(undefined, { persist: "none", emit: false });
return dropped;
});
}
async completeGoalFromTool(): Promise<Goal> {
return await this.#withAccounting(async () => {
await this.#flushUsageLocked("suppressed");
const state = this.#getStateClone();
if (!state?.goal) {
throw new Error("cannot complete goal because no goal is active");
}
if (state.goal.status === "complete") {
throw new Error("goal is already complete");
}
if (state.goal.status === "dropped") {
throw new Error("cannot complete a dropped goal");
}
state.enabled = false;
state.goal.status = "complete";
state.goal.updatedAt = this.#now();
state.mode = "exiting";
state.reason = "completed";
this.#clearActiveAccounting();
this.#budgetReportedFor = undefined;
await this.#commitState(state, { persist: "goal" });
return state.goal;
});
}
buildActivePrompt(): string | undefined {
const state = this.#host.getState();
return state?.enabled && state.goal && state.goal.status === "active"
? renderGoalPrompt("active", state.goal)
: undefined;View on GitHub (pinned to 9690622007)
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
Example fix
// before
await dropGoal();
await runtime.completeGoalFromTool(); // throws: dropped
// after
await dropGoal();
const state = host.getState();
if (state?.goal?.status === 'active' || state?.goal?.status === 'budget-limited') {
await runtime.completeGoalFromTool();
} Defensive patterns
Strategy: validation
Validate before calling
const state = host.getState();
if (state?.goal?.status === 'dropped') throw new Error('goal was dropped; cannot complete'); Type guard
function isCompletable(state: { goal?: { status: string } } | undefined | null): boolean {
return !!state?.goal && !['complete', 'dropped'].includes(state.goal.status);
} Try / catch
try {
await runtime.completeGoalFromTool();
} catch (err) {
if (err instanceof Error && err.message.includes('dropped goal')) {
return { ok: false, reason: 'goal-dropped' }; // cancellation already decided
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- cannot create a new goal because this session already has a
- cannot replace goal because no goal is active
- No paused goal.
- Goal is already complete.
- cannot complete goal because no goal is active
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8fa5d4aec44f2e81.
Report an issue: GitHub.