{"record":{"id":"897cec579a1e4e19","repo":"rohitg00/ai-engineering-from-scratch","slug":"budget-step-requires-non-negative-tokens-and-dolla","errorCode":null,"errorMessage":"Budget.step requires non-negative tokens and dollars","messagePattern":"Budget\\.step requires non-negative tokens and dollars","errorType":"exception","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"phases/19-capstone-projects/01-terminal-native-coding-agent/code/ts/src/plan.ts","lineNumber":41,"sourceCode":"    const lines = [`GOAL: ${this.goal}`];\n    for (const it of this.items) {\n      lines.push(`  [${mark[it.status]}] ${it.id}. ${it.description}`);\n    }\n    return lines.join(\"\\n\");\n  }\n}\n\nexport class Budget {\n  maxTurns = 50;\n  maxTokens = 200_000;\n  maxDollars = 5.0;\n  turnsUsed = 0;\n  tokensUsed = 0;\n  dollarsUsed = 0;\n\n  step(tokens: number, dollars: number): void {\n    if (tokens < 0 || dollars < 0) {\n      throw new RangeError(\"Budget.step requires non-negative tokens and dollars\");\n    }\n    this.turnsUsed += 1;\n    this.tokensUsed += tokens;\n    this.dollarsUsed += dollars;\n  }\n\n  exceeded(): string | null {\n    if (this.turnsUsed >= this.maxTurns) return \"turn_limit\";\n    if (this.tokensUsed >= this.maxTokens) return \"token_limit\";\n    if (this.dollarsUsed >= this.maxDollars) return \"dollar_limit\";\n    return null;\n  }\n\n  snapshot(): { turnsUsed: number; tokensUsed: number; dollarsUsed: number } {\n    return {\n      turnsUsed: this.turnsUsed,\n      tokensUsed: this.tokensUsed,\n      dollarsUsed: this.dollarsUsed,","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/rohitg00/ai-engineering-from-scratch/blob/39ea8a1c6d0b61f071226eff7ede4d4105fed820/phases/19-capstone-projects/01-terminal-native-coding-agent/code/ts/src/plan.ts#L23-L59","documentation":"Budget.step throws a RangeError when passed a negative token count or dollar amount; the agent's per-run budget ledger refuses to record negative usage so it can never be inflated into a refund. Called from the main agent loop for every model turn.","triggerScenarios":"Calling step(-100, 0.01), step(500, -0.02), or passing NaN comparisons gone wrong (NaN fails the <0 check but still poisons totals only if negative). Specifically any invocation where tokens<0 or dollars<0.","commonSituations":"Mocked model clients returning negative usage fields, refund/credit accounting wired directly into step, or computing deltas where a later reading is subtracted from an earlier one.","solutions":["Clamp usage to 0 before calling step: step(Math.max(0,t), Math.max(0,d))","Fix the provider adapter that produced negative usage numbers","Model refunds as a separate ledger method, not negative step()"],"exampleFix":"// before\nbudget.step(usage.tokens - previousTokens, usage.dollars);\n// after\nbudget.step(Math.max(0, usage.tokens - previousTokens), Math.max(0, usage.dollars));","handlingStrategy":"validation","validationCode":"if (tokens < 0 || dollars < 0) return; // or clamp \nbudget.step(Math.max(0, tokens), Math.max(0, dollars));","typeGuard":null,"tryCatchPattern":"try { budget.step(t, d); } catch (e) { if (e instanceof RangeError) logBadUsage(t, d); else throw e; }","preventionTips":["Validate provider usage fields before recording them","Keep refunds in a separate ledger field, never negative step()"],"tags":["typescript","validation","range-error","budget"],"backgroundTag":"argument-out-of-range","analyzedSha":"39ea8a1c6d0b61f071226eff7ede4d4105fed820","analyzedAt":"2026-08-26T03:13:46.626Z","schemaVersion":2},"datasetVersion":"2026-08-26T07:17:17.940Z"}