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

  1. Treat the goal as done; create a new goal via createGoal if more work is needed
  2. Check state.goal.status !== 'complete' before resuming
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/905f6b9d7e89c06c. Report an issue: GitHub.