can1357/oh-my-pi · error · Error

goal token_budget must be a positive integer when provided

Error message

goal token_budget must be a positive integer when provided

What it means

validateTokenBudget guards the optional tokenBudget argument passed to goal create/replace operations. It throws when the value is provided but is not a positive whole number (non-integer, zero, or negative), because a budget must represent a countable, spendable token allowance.

Source

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

		timeUsedSeconds: String(goal.timeUsedSeconds),
	});
}

export function completionBudgetReport(goal: Goal): string | null {
	const parts: string[] = [];
	if (goal.tokenBudget !== undefined) {
		parts.push(`tokens used: ${goal.tokensUsed} of ${goal.tokenBudget}`);
	}
	if (goal.timeUsedSeconds > 0) {
		parts.push(`time used: ${goal.timeUsedSeconds} seconds`);
	}
	if (parts.length === 0) return null;
	return `Goal achieved. Report final budget usage to the user: ${parts.join("; ")}.`;
}

function validateTokenBudget(tokenBudget: number | undefined): void {
	if (tokenBudget !== undefined && (!Number.isInteger(tokenBudget) || tokenBudget <= 0)) {
		throw new Error("goal token_budget must be a positive integer when provided");
	}
}

function isAccountingStatus(goal: Goal): boolean {
	return goal.status === "active" || goal.status === "budget-limited";
}

export class GoalRuntime {
	readonly #host: GoalRuntimeHost;
	#turnSnapshot: GoalTurnSnapshot | undefined;
	#wallClock: GoalWallClockSnapshot;
	#budgetReportedFor: string | undefined;
	#accountingTail: Promise<void> = Promise.resolve();

	constructor(host: GoalRuntimeHost) {
		this.#host = host;
		this.#wallClock = { lastAccountedAt: this.#now() };
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Omit tokenBudget entirely (undefined) if no budget is wanted instead of passing 0
  2. Round or validate the value: only pass positive integers
  3. Convert string input with Number.parseInt/Number() and check Number.isInteger before calling
  4. Fix config/CLI parsing so token_budget is deserialized as a number

Example fix

// before
await goals.createGoal({ objective: 'ship v2', tokenBudget: 0 });
// after
const budget = Number(process.env.GOAL_TOKEN_BUDGET);
await goals.createGoal({
  objective: 'ship v2',
  tokenBudget: Number.isInteger(budget) && budget > 0 ? budget : undefined,
});
Defensive patterns

Strategy: validation

Validate before calling

function isValidTokenBudget(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v > 0;
}
// call site: const tokenBudget = isValidTokenBudget(raw) ? raw : undefined;

Type guard

function isValidTokenBudget(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v > 0;
}

Try / catch

try {
  await runtime.createGoal({ objective, tokenBudget });
} catch (err) {
  if (err instanceof Error && err.message.includes('token_budget must be a positive integer')) {
    tokenBudget = undefined; // retry without budget
  } else throw err;
}

Prevention

When it happens

Trigger: Calling createGoal or replaceGoal with tokenBudget set to 0, a negative number, a float (e.g. 1000.5), NaN, or a non-number value that slipped past typing; validateTokenBudget is also invoked via onBudgetMutated.

Common situations: Parsing token_budget from CLI args or JSON config where the value arrives as a string or float; computing a budget from a formula that yields a fractional value; defaulting to 0 as a sentinel for 'unset' instead of undefined.

Understand the failure class

Background: "must be positive", "Invalid value": how libraries reject invalid parameter values (ValueError, ArgumentError, INVALID_PARAMETER_VALUE) — this error's family across 28 libraries.

Related errors


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