can1357/oh-my-pi · warning · Error
goal is already complete
Error message
goal is already complete
What it means
completeGoalFromTool throws when the goal's status is already 'complete'. Completion is a terminal transition; calling it again would double-finalize the goal and corrupt usage accounting, so the runtime rejects the second call.
Source
Thrown at packages/coding-agent/src/goals/runtime.ts:481
await this.#host.emit({
type: "goal_updated",
goal: dropped,
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();View on GitHub (pinned to 9690622007)
Solutions
- Make completion idempotent in the caller: check status === 'complete' first and treat it as success
- Suppress duplicate tool invocations in the harness before they reach the runtime
- Catch this error and no-op instead of surfacing it as a failure
Example fix
// before
await runtime.completeGoalFromTool(); // throws on repeat
// after
const state = host.getState();
if (state?.goal?.status !== 'complete') {
await runtime.completeGoalFromTool();
} Defensive patterns
Strategy: try-catch
Validate before calling
const state = host.getState();
if (state?.goal?.status === 'complete') return { ok: true, alreadyComplete: true }; 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 === 'goal is already complete') {
return; // idempotent success — completion already recorded
}
throw err;
} Prevention
- Deduplicate tool calls before dispatch (guard against LLM re-emitting finish tools)
- Make retry wrappers treat 'already complete' as success
- Check goal status before every completion call
When it happens
Trigger: Calling completeGoalFromTool twice (duplicate tool invocation); retrying the completion op after a network/persistence error that actually succeeded; agent re-emitting the completion tool in a follow-up turn.
Common situations: Tool retries at the harness level; LLM loops that re-call finish tools; UI double-click on a complete button.
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/e27fb7711665b89c.
Report an issue: GitHub.