{"record":{"id":"e125f95eccb857c9","repo":"can1357/oh-my-pi","slug":"goal-token-budget-must-be-a-positive-integer-when","errorCode":null,"errorMessage":"goal token_budget must be a positive integer when provided","messagePattern":"goal token_budget must be a positive integer when provided","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/goals/runtime.ts","lineNumber":109,"sourceCode":"\t\ttimeUsedSeconds: String(goal.timeUsedSeconds),\n\t});\n}\n\nexport function completionBudgetReport(goal: Goal): string | null {\n\tconst parts: string[] = [];\n\tif (goal.tokenBudget !== undefined) {\n\t\tparts.push(`tokens used: ${goal.tokensUsed} of ${goal.tokenBudget}`);\n\t}\n\tif (goal.timeUsedSeconds > 0) {\n\t\tparts.push(`time used: ${goal.timeUsedSeconds} seconds`);\n\t}\n\tif (parts.length === 0) return null;\n\treturn `Goal achieved. Report final budget usage to the user: ${parts.join(\"; \")}.`;\n}\n\nfunction validateTokenBudget(tokenBudget: number | undefined): void {\n\tif (tokenBudget !== undefined && (!Number.isInteger(tokenBudget) || tokenBudget <= 0)) {\n\t\tthrow new Error(\"goal token_budget must be a positive integer when provided\");\n\t}\n}\n\nfunction isAccountingStatus(goal: Goal): boolean {\n\treturn goal.status === \"active\" || goal.status === \"budget-limited\";\n}\n\nexport class GoalRuntime {\n\treadonly #host: GoalRuntimeHost;\n\t#turnSnapshot: GoalTurnSnapshot | undefined;\n\t#wallClock: GoalWallClockSnapshot;\n\t#budgetReportedFor: string | undefined;\n\t#accountingTail: Promise<void> = Promise.resolve();\n\n\tconstructor(host: GoalRuntimeHost) {\n\t\tthis.#host = host;\n\t\tthis.#wallClock = { lastAccountedAt: this.#now() };\n\t}","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/goals/runtime.ts#L91-L127","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Omit tokenBudget entirely (undefined) if no budget is wanted instead of passing 0","Round or validate the value: only pass positive integers","Convert string input with Number.parseInt/Number() and check Number.isInteger before calling","Fix config/CLI parsing so token_budget is deserialized as a number"],"exampleFix":"// before\nawait goals.createGoal({ objective: 'ship v2', tokenBudget: 0 });\n// after\nconst budget = Number(process.env.GOAL_TOKEN_BUDGET);\nawait goals.createGoal({\n  objective: 'ship v2',\n  tokenBudget: Number.isInteger(budget) && budget > 0 ? budget : undefined,\n});","handlingStrategy":"validation","validationCode":"function isValidTokenBudget(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v) && v > 0;\n}\n// call site: const tokenBudget = isValidTokenBudget(raw) ? raw : undefined;","typeGuard":"function isValidTokenBudget(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v) && v > 0;\n}","tryCatchPattern":"try {\n  await runtime.createGoal({ objective, tokenBudget });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('token_budget must be a positive integer')) {\n    tokenBudget = undefined; // retry without budget\n  } else throw err;\n}","preventionTips":["Use undefined (not 0) as the 'no budget' sentinel","Coerce and validate numeric config values at the boundary (Number.isInteger, > 0)","Type the input as number | undefined so strings/floats fail at compile time"],"tags":["validation","arguments","goals"],"backgroundTag":"invalid-parameter-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}