can1357/oh-my-pi · error · Error

objective is required when op=replace

Error message

objective is required when op=replace

What it means

replaceGoal requires a non-empty objective string, mirroring createGoal. The runtime trims the input and throws when nothing remains, since replacing a goal with no objective would leave the tracker without a target.

Source

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

		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");
			}
			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();

View on GitHub (pinned to 9690622007)

Solutions

  1. Supply a non-empty objective when calling replaceGoal
  2. Validate/trim the objective in the caller before invoking the runtime
  3. Reject blank objective at the input schema level

Example fix

// before
await runtime.replaceGoal({ objective: draft });
// after
const objective = draft.trim();
if (!objective) throw new Error('New goal objective must not be empty');
await runtime.replaceGoal({ objective });
Defensive patterns

Strategy: validation

Validate before calling

if (typeof objective !== 'string' || objective.trim().length === 0) {
  throw new Error('objective must be a non-empty string');
}

Type guard

function hasObjective(input: { objective: string }): boolean {
  return typeof input.objective === 'string' && input.objective.trim().length > 0;
}

Try / catch

try {
  await runtime.replaceGoal({ objective });
} catch (err) {
  if (err instanceof Error && err.message === 'objective is required when op=replace') {
    return { ok: false, reason: 'empty-objective' };
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling replaceGoal with objective '' or whitespace-only; dropping the objective field when building tool arguments; upstream code joining an empty array into ''.

Common situations: Interactive goal-edit flows submitted with a blank field; LLM tool calls missing the objective argument; templates with unfilled placeholders.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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