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
- Call createGoal instead if no goal is active
- Call resumeGoal instead if the goal is merely paused
- Check that getState().enabled is true and goal.status is 'active' or 'budget-limited' before replacing
- 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
- Gate the replace action on getState(): enabled && status in {active, budget-limited}
- Map user intent correctly: no goal → create, paused → resume, live → replace
- Persist and restore goal state so restarts do not silently drop active goals
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
- cannot create a new goal because this session already has a
- cannot complete a dropped goal
- 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/48d8a09b674bcc1f.
Report an issue: GitHub.