can1357/oh-my-pi · error · Error

cannot complete goal because no goal is active

Error message

cannot complete goal because no goal is active

What it means

completeGoalFromTool finalizes the session's active goal, so it throws when no goal exists in the state — there is nothing to mark complete. It is the tool-invoked completion path and requires a live GoalModeState with a goal.

Source

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

			const dropped = { ...state.goal, status: "dropped" as const, updatedAt: this.#now() };
			this.#clearActiveAccounting();
			this.#budgetReportedFor = undefined;
			await this.#host.emit({
				type: "goal_updated",
				goal: dropped,
				state: { ...state, enabled: false, goal: dropped },
			});
			await this.#commitState(undefined, { persist: "none", emit: false });
			return dropped;
		});
	}

	async completeGoalFromTool(): Promise<Goal> {
		return await this.#withAccounting(async () => {
			await this.#flushUsageLocked("suppressed");
			const state = this.#getStateClone();
			if (!state?.goal) {
				throw new Error("cannot complete goal because no goal is active");
			}
			if (state.goal.status === "complete") {
				throw new Error("goal is already complete");
			}
			if (state.goal.status === "dropped") {
				throw new Error("cannot complete a dropped goal");
			}
			state.enabled = false;
			state.goal.status = "complete";
			state.goal.updatedAt = this.#now();
			state.mode = "exiting";
			state.reason = "completed";
			this.#clearActiveAccounting();
			this.#budgetReportedFor = undefined;
			await this.#commitState(state, { persist: "goal" });
			return state.goal;
		});
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure a goal exists (createGoal) before attempting completion
  2. Check getState()?.goal before emitting the complete op
  3. Fix the agent flow so the completion tool is only exposed while a goal is active

Example fix

// before
await runtime.completeGoalFromTool(); // throws if none
// after
const state = host.getState();
if (!state?.goal) throw new Error('No goal to complete');
await runtime.completeGoalFromTool();
Defensive patterns

Strategy: validation

Validate before calling

const state = host.getState();
if (!state?.goal) throw new Error('completeGoalFromTool requires an existing goal');

Type guard

function hasGoal(state: { goal?: unknown } | undefined | null): boolean {
  return !!state?.goal;
}

Try / catch

try {
  await runtime.completeGoalFromTool();
} catch (err) {
  if (err instanceof Error && err.message.includes('no goal is active')) {
    return { ok: false, reason: 'no-goal' }; // surface as no-op to the agent
  }
  throw err;
}

Prevention

When it happens

Trigger: Invoking the goal-complete tool on a session with no goal created; calling it after the goal was dropped (dropped has its own message) or after state was cleared by a restart; double-invocation after state reset.

Common situations: LLM emitting the completion tool without the goal op having run; session restored without goal persistence; flows that complete the goal then call the tool again in a new context.

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/51b3ef06f2269256. Report an issue: GitHub.