can1357/oh-my-pi · error · Error

cannot create a new goal because this session already has a

Error message

cannot create a new goal because this session already has a goal

What it means

Goal mode enforces one live goal per session. createGoal throws if the session already has a goal whose status is anything other than 'dropped' or 'complete' (e.g. active, budget-limited, or paused), because stacking goals would corrupt budget accounting.

Source

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

			objective,
			status: "active",
			tokenBudget,
			tokensUsed: 0,
			timeUsedSeconds: 0,
			createdAt: now,
			updatedAt: now,
		};
		return { enabled: true, mode: "active", goal };
	}

	async createGoal(input: { objective: string; tokenBudget?: number }): Promise<GoalModeState> {
		const objective = input.objective.trim();
		if (!objective) throw new Error("objective is required when op=create");
		validateTokenBudget(input.tokenBudget);
		return await this.#withAccounting(async () => {
			const existing = this.#host.getState();
			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");
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Call the drop operation on the current goal before creating a new one
  2. Use replaceGoal instead of createGoal to swap in a new objective
  3. Complete the existing goal first if it was actually achieved
  4. Check host.getState().goal.status before attempting creation

Example fix

// before
await runtime.createGoal({ objective: newObjective }); // throws if goal exists
// after
const state = runtime.getState?.() ?? host.getState();
if (state?.goal && state.goal.status !== 'dropped' && state.goal.status !== 'complete') {
  await runtime.replaceGoal({ objective: newObjective });
} else {
  await runtime.createGoal({ objective: newObjective });
}
Defensive patterns

Strategy: type-guard

Validate before calling

const state = host.getState();
const canCreate = !state?.goal || state.goal.status === 'dropped' || state.goal.status === 'complete';
if (!canCreate) await dropOrReplaceFirst();

Type guard

function canCreateGoal(state: { goal?: { status: string } } | undefined | null): boolean {
  return !state?.goal || state.goal.status === 'dropped' || state.goal.status === 'complete';
}

Try / catch

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

Prevention

When it happens

Trigger: Calling createGoal twice without dropping or completing the first goal; a persisted goal from a resumed session is still active; calling create when the intended op was replaceGoal.

Common situations: Scripts that retry goal creation after an unrelated error; users typing a new goal while the previous one is still tracked; forgetting to call the drop op before starting fresh.

Related errors


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