can1357/oh-my-pi · error · Error

cannot replace goal because no goal is active

Error message

cannot replace goal because no goal is active

What it means

replaceGoal only works when goal mode is enabled and the existing goal is in an accounting status ('active' or 'budget-limited'). It throws when there is no goal at all, goal mode is disabled, or the goal is already complete/dropped.

Source

Thrown at packages/coding-agent/src/goals/runtime.ts:408

			if (existing?.goal && existing.goal.status !== "dropped" && existing.goal.status !== "complete") {
				throw new Error("cannot create a new goal because this session already has a goal");
			}
			const state = this.#createGoalState(objective, input.tokenBudget);
			this.#budgetReportedFor = undefined;
			this.#markActiveAccounting(state.goal);
			await this.#commitState(state, { persist: "goal" });
			return state;
		});
	}

	async replaceGoal(input: { objective: string; tokenBudget?: number }): Promise<GoalModeState> {
		const objective = input.objective.trim();
		if (!objective) throw new Error("objective is required when op=replace");
		validateTokenBudget(input.tokenBudget);
		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;

View on GitHub (pinned to 9690622007)

Solutions

  1. Call createGoal instead if no goal is active
  2. Call resumeGoal instead if the goal is merely paused
  3. Check that getState().enabled is true and goal.status is 'active' or 'budget-limited' before replacing
  4. Fix flow logic so replace is only offered while a goal is live

Example fix

// before
await runtime.replaceGoal({ objective }); // throws when none active
// after
const state = host.getState();
if (state?.enabled && (state.goal.status === 'active' || state.goal.status === 'budget-limited')) {
  await runtime.replaceGoal({ objective });
} else {
  await runtime.createGoal({ objective });
}
Defensive patterns

Strategy: type-guard

Validate before calling

const state = host.getState();
const active = state?.enabled && (state.goal.status === 'active' || state.goal.status === 'budget-limited');
if (!active) throw new Error('replaceGoal requires an active goal');

Type guard

function hasActiveGoal(state: { enabled: boolean; goal: { status: string } } | undefined | null): boolean {
  return !!state?.enabled && (state.goal.status === 'active' || state.goal.status === 'budget-limited');
}

Try / catch

try {
  await runtime.replaceGoal({ objective });
} catch (err) {
  if (err instanceof Error && err.message.includes('no goal is active')) {
    await runtime.createGoal({ objective });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling replaceGoal on a fresh session with no goal; replacing after the goal was dropped or completed; goal mode paused/disabled via the pause path; resuming with replace instead of resumeGoal.

Common situations: Blind retry logic that calls replace without checking state; a session restart where the goal never persisted; confusion between resume (paused goal) and replace (live goal) ops.

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/48d8a09b674bcc1f. Report an issue: GitHub.