can1357/oh-my-pi · error · Error
Goal is already complete.
Error message
Goal is already complete.
What it means
resumeGoal refuses to resume a goal whose status is 'complete'. A completed goal is finalized (mode disabled, reason 'completed'); resuming it would contradict the recorded outcome, so the runtime throws instead.
Source
Thrown at packages/coding-agent/src/goals/runtime.ts:423
return await this.#withAccounting(async () => {
const existing = this.#host.getState();
if (!existing?.enabled || !isAccountingStatus(existing.goal)) {
throw new Error("cannot replace goal because no goal is active");
}
await this.#flushUsageLocked("suppressed");
const state = this.#createGoalState(objective, input.tokenBudget);
this.#budgetReportedFor = undefined;
this.#markActiveAccounting(state.goal);
await this.#commitState(state, { persist: "goal" });
return state;
});
}
async resumeGoal(): Promise<GoalModeState> {
return await this.#withAccounting(async () => {
const state = this.#getStateClone();
if (!state?.goal) throw new Error("No paused goal.");
if (state.goal.status === "complete") throw new Error("Goal is already complete.");
state.enabled = true;
state.mode = "active";
state.reason = undefined;
state.goal.status = "active";
state.goal.updatedAt = this.#now();
this.#budgetReportedFor = undefined;
this.#markActiveAccounting(state.goal);
await this.#commitState(state, { persist: "goal" });
return state;
});
}
async pauseGoal(): Promise<GoalModeState | undefined> {
return await this.#withAccounting(async () => {
await this.#flushUsageLocked("suppressed");
const state = this.#getStateClone();
if (!state?.goal) return undefined;
state.enabled = false;View on GitHub (pinned to 9690622007)
Solutions
- Treat the goal as done; create a new goal via createGoal if more work is needed
- Check state.goal.status !== 'complete' before resuming
- Disable the resume action when status is 'complete'
Example fix
// before
await runtime.resumeGoal(); // throws when complete
// after
const state = host.getState();
if (state?.goal?.status === 'complete') {
await runtime.createGoal({ objective: nextObjective });
} else {
await runtime.resumeGoal();
} Defensive patterns
Strategy: validation
Validate before calling
const state = host.getState();
if (state?.goal?.status === 'complete') throw new Error('goal already complete; create a new one'); Type guard
function isResumable(state: { goal?: { status: string } } | undefined | null): boolean {
return !!state?.goal && state.goal.status !== 'complete';
} Try / catch
try {
await runtime.resumeGoal();
} catch (err) {
if (err instanceof Error && err.message === 'Goal is already complete.') {
return { ok: false, reason: 'already-complete' }; // not an error for the user
}
throw err;
} Prevention
- Treat 'complete' as terminal in all goal UI flows
- Make resume handlers idempotent: check status first
- After completion, switch the UI to new-goal creation instead of resume
When it happens
Trigger: Calling resumeGoal after completeGoalFromTool finished the goal; resuming a persisted session whose goal already reached 'complete'; retry logic that calls resume after the tool reported completion.
Common situations: UI offering resume on a finished goal; scripts looping over pause/resume without re-checking status; user wanting to keep working after completion (needs a new goal instead).
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.
- cannot complete goal because no goal is active
- goal is already complete
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/905f6b9d7e89c06c.
Report an issue: GitHub.