Add budget tracking and enforcement for sandbox sessions (v1.1)#52
Add budget tracking and enforcement for sandbox sessions (v1.1)#52
Conversation
Implements comprehensive budget tracking and enforcement: - Add BudgetConfig, BudgetStatus, BudgetWarning types and BudgetExceededError class - Create ConfigManager for persistent settings (~/.parallel-cc/config.json) - Create BudgetTracker for period-based spending tracking (daily/weekly/monthly) - Add budget enforcement in SandboxManager with warnings at 50%/80% thresholds - Add CLI commands: config set/get/list, budget-status - Add --budget flag to sandbox-run command - Add budget checking during execution monitoring in ClaudeRunner - Add database methods for budget tracking records Test coverage: - 37 tests for ConfigManager - 38 tests for BudgetTracker - 25 tests for budget enforcement in SandboxManager https://claude.ai/code/session_01CDYWMZJdg8FH55bgQzNmds
WalkthroughAdds a budget subsystem: persistent typed config, DB persistence and APIs for budget tracking, a BudgetTracker class, runtime sandbox enforcement (soft warnings and hard termination), CLI commands/options for budget control, types/errors for budgeting, and comprehensive tests. Changes
Sequence DiagramssequenceDiagram
actor User
participant CLI as "CLI"
participant CM as "ConfigManager"
participant BT as "BudgetTracker"
participant DB as "SessionDB"
participant SM as "SandboxManager"
User->>CLI: budget-status
CLI->>CM: getBudgetConfig()
CM-->>CLI: budgetConfig
CLI->>BT: generateBudgetStatus(period?)
BT->>DB: getOrCreateBudgetTrackingRecord(monthly)
DB-->>BT: budgetRecord
BT->>DB: getActiveSessions()
DB-->>BT: sessions[]
BT-->>CLI: BudgetStatus
CLI-->>User: formatted report
sequenceDiagram
participant ClaudeRunner
participant SM as "SandboxManager"
participant BT as "BudgetTracker"
participant DB as "SessionDB"
participant E2B as "Sandbox"
ClaudeRunner->>SM: monitorExecution / periodic check
SM->>SM: calculateEstimatedCost(elapsed)
SM->>BT: checkMonthlyBudget(estimated)
BT->>DB: getOrCreateBudgetTrackingRecord(monthly)
DB-->>BT: budgetRecord
BT-->>SM: BudgetCheckResult
alt Exceeded
SM-->>ClaudeRunner: throw BudgetExceededError
ClaudeRunner->>E2B: terminate sandbox
else Soft warning
SM-->>ClaudeRunner: return BudgetWarning (log)
ClaudeRunner->>E2B: continue execution
else Within limit
SM-->>ClaudeRunner: null
ClaudeRunner->>E2B: continue execution
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Add budget tracking and enforcement for sandbox sessions by terminating over-budget runs in
|
Code Review: Budget Tracking and Enforcement (v1.1)✅ Overall AssessmentThis is a well-implemented feature that adds comprehensive budget tracking capabilities to parallel-cc. The code follows existing patterns, is well-typed, and includes comprehensive tests. ✅ Strengths
|
|
I've posted a comprehensive review of PR #52 covering code quality, potential bugs, performance considerations, security concerns, and test coverage. The review highlights:
The recommendation is to approve with minor follow-ups for the float precision issue and pricing configuration. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/cli.ts`:
- Around line 3175-3242: The SessionDB instance (db) may not be closed if an
exception is thrown before the final db.close(); wrap the creation/use of
SessionDB in a try/finally so db.close() always runs: after constructing
SessionDB() (and before operations like db.hasBudgetTrackingTable,
db.migrateToLatest, BudgetTracker, tracker.generateBudgetStatus), ensure a
finally block calls db.close(); also guard against db being undefined/null in
the finally (check db) so no exceptions occur while closing.
🧹 Nitpick comments (5)
src/config.ts (1)
24-31: Consider renaming internalConfiginterface to avoid confusion.The local
Configinterface here has the same name as the exportedConfiginterface insrc/types.ts(lines 128-134), but they have different shapes. While this is scoped locally and won't cause runtime issues, it could confuse developers navigating the codebase.💡 Suggested rename
-interface Config { +interface AppConfig { budget: BudgetConfig; [key: string]: unknown; } -const DEFAULT_CONFIG: Config = { +const DEFAULT_CONFIG: AppConfig = { budget: DEFAULT_BUDGET_CONFIG };And update references in the class.
src/db.ts (1)
1689-1699: Potential race condition ingetOrCreateBudgetTrackingRecord.The get-then-create pattern has a TOCTOU (time-of-check to time-of-use) race condition if two processes call this simultaneously. However, for a single-user CLI tool, this is likely acceptable.
If concurrent access becomes a concern, consider using
INSERT OR IGNOREwith a unique constraint, or wrapping in a transaction.src/budget-tracker.ts (1)
145-151: Consider verifying session existence before recording cost.
recordSessionCostcallsdb.updateSessionCost()which may silently fail if the session ID doesn't exist (returnsfalse). Consider logging a warning when the update fails.💡 Suggested improvement
recordSessionCost(sessionId: string, cost: number): void { // Update session cost estimate - this.db.updateSessionCost(sessionId, cost); + const updated = this.db.updateSessionCost(sessionId, cost); + if (!updated) { + // Session may have been deleted or ID is invalid + // Still update monthly tracking to maintain accurate totals + } // Update monthly tracking this.recordCost(cost, 'monthly'); }src/e2b/claude-runner.ts (1)
1442-1460: Consider usinginstanceoffor type-safe error checking.The budget check uses
error.name === 'BudgetExceededError'for identification. While this works, usinginstanceofwould be more type-safe and would catch subclasses. However,BudgetExceededErrorwould need to be imported from../types.js.The current approach is acceptable if there are concerns about import cycles or if the error might cross module boundaries where
instanceoffails.💡 Alternative with instanceof
Add import at top of file:
import { BudgetExceededError } from '../types.js';Then update the check:
- if (budgetError instanceof Error && budgetError.name === 'BudgetExceededError') { + if (budgetError instanceof BudgetExceededError) {
LGTM on the overall integration!
The budget check is well-integrated into the monitoring flow:
- Positioned after timeout checks (correct priority)
- Soft warnings logged without termination
- Hard violations (BudgetExceededError) trigger termination
- Other errors handled gracefully without disrupting execution
Based on learnings, this aligns with the requirement to "Track cost estimation and actual costs for E2B sandbox execution with budget limits".
src/cli.ts (1)
3036-3084: Consider using a more robust key normalization approach.The current string replacement chain for normalizing kebab-case to camelCase only handles three specific keys. If more budget-related keys are added in the future, this approach could become error-prone.
💡 Suggested improvement
- // Convert key from kebab-case to camelCase for budget keys - const normalizedKey = key.replace('monthly-limit', 'monthlyLimit') - .replace('per-session-default', 'perSessionDefault') - .replace('warning-thresholds', 'warningThresholds'); + // Convert key from kebab-case to camelCase for budget keys + const normalizedKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
Fixes the following issues identified in code review: 1. Float precision bug: Convert warning threshold Set keys to integer percentages (0-100) to avoid floating-point comparison issues with values like 0.8 2. Hard-coded pricing: Move E2B hourly rate ($0.10/hour) to configurable e2bHourlyRate in BudgetConfig, allowing updates when pricing changes 3. Synchronous I/O: Add debounced writes to ConfigManager (100ms window) with flushSync() for immediate persistence and cancelPendingWrites() for clean test teardown 4. UTC documentation: Add comprehensive JSDoc explaining that period calculations use UTC dates, with timezone implications noted 5. Warning reset: Add automatic warning reset when budget period rolls over (e.g., new month starts) https://claude.ai/code/session_01CDYWMZJdg8FH55bgQzNmds
Code Review: Budget Tracking and Enforcement (v1.1)Overall AssessmentThis is a well-architected feature that adds comprehensive budget tracking capabilities. The code follows existing patterns, has strong typing, and includes extensive test coverage. Several good design decisions were made (e.g., using integer percentages for Set keys, period rollover warning reset). I'm focusing on issues not already covered in previous reviews. 🔴 Critical Issues1. Database Connection Leak in Error Path ()Issue: If an error occurs during migration (lines 3180-3183) or before , the connection is never closed. Impact: Multiple failed CLI calls could exhaust database connections. Fix: Use try/finally or close in catch block. 🟡 Medium Priority Issues2. Potential Type Violation in Sandbox Budget Warnings ()Issue: The check uses a float comparison, but the Set key is an integer. This means:
Impact: Edge case where warnings might fire at slightly different percentages than expected. Recommendation: This is minor and likely acceptable for budget warnings, but worth noting. 3. Unsafe Config Set Implementation ()Issues:
Recommendation: Add key validation and type checking for budget fields. 🟢 Low Priority / Observations4. Debounce Implementation Could Be Improved ()Observation: The directory check is a good defensive pattern, but if the directory is deleted mid-execution (unlikely), we silently skip the write. This is acceptable for a CLI tool. 5. Budget Warning Period Rollover ()Good: This is a nice implementation - warnings are reset when the period rolls over, avoiding duplicate warnings across periods. Note: This works correctly for single BudgetTracker instance but could miss rollovers if multiple instances are created (unlikely in practice). ✅ Well-Implemented Features
📊 Test CoverageExcellent: 462 tests in budget-tracker.test.ts, 426 in config.test.ts - well above the project's >85% threshold. Minor gap: No tests for concurrent BudgetTracker instances (unlikely in practice but worth noting) 🎯 Recommendations SummaryHigh Priority (fix before merge):
Medium Priority (post-merge follow-ups): Low Priority: OverallRecommendation: ✅ Approve with critical database cleanup fix The code is production-quality with one critical bug (database connection leak) that should be fixed before merge. The feature is well-designed, thoroughly tested, and follows project conventions. The budget tracking system will be a valuable addition for users managing E2B sandbox costs. |
Code Review: Budget Tracking and Enforcement (v1.1)Overall AssessmentThis is a well-architected feature that adds comprehensive budget tracking capabilities. The code follows existing patterns, has strong typing, and includes extensive test coverage. Several good design decisions were made (e.g., using integer percentages for Set keys, period rollover warning reset). I am focusing on issues not already covered in previous reviews. Critical Issues1. Database Connection Leak in Error Path (cli.ts:3177-3241)The budget-status command does not properly close the database connection when errors occur during migration: program.command("budget-status").action(async (options) => {
try {
const db = new SessionDB();
// ... migration, tracking logic ...
db.close(); // Line 3232 - only called on success
} catch (error) {
// ... error handling ...
process.exit(1); // Database not closed!
}
});Issue: If an error occurs during migration (lines 3180-3183) or before db.close(), the connection is never closed. Impact: Multiple failed CLI calls could exhaust database connections. Fix: Use try/finally or close in catch block. Medium Priority Issues2. Potential Type Violation in Sandbox Budget Warnings (sandbox-manager.ts:436-437)const thresholdInt = Math.round(threshold * 100);
if (percentUsed >= threshold && !warningsIssued.has(thresholdInt)) {
warningsIssued.add(thresholdInt);Issue: The check uses a float comparison but the Set key is an integer. If threshold = 0.8 and percentUsed = 0.805, the comparison passes, thresholdInt = 80 is added to Set. But percentUsed = 0.805 is actually 80.5% - the rounding masks this precision loss. Impact: Edge case where warnings might fire at slightly different percentages than expected. Recommendation: This is minor and likely acceptable for budget warnings, but worth noting. 3. Unsafe Config Set Implementation (config.ts:199-217)The set() method lacks validation: set(key: string, value: unknown): void {
const parts = key.split(".");
let current: Record<string, unknown> = this.config;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (current[part] === undefined || current[part] === null || typeof current[part] !== "object") {
current[part] = {}; // Creates objects on the fly
}
current = current[part] as Record<string, unknown>;
}
// No validation of dangerous keys or value type
const lastPart = parts[parts.length - 1];
current[lastPart] = value;
}Issues:
Recommendation: Add key validation and type checking for budget fields. Low Priority / Observations4. Debounce Implementation (config.ts:143-157)The flushSync() method has a defensive directory check: if (existsSync(dir)) {
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
}Observation: If the directory is deleted mid-execution (unlikely), we silently skip the write. This is acceptable for a CLI tool. 5. Budget Warning Period Rollover (budget-tracker.ts:128-134)const periodKey = `${period}:${periodStart}`;
if (this.currentPeriodKey !== null && this.currentPeriodKey !== periodKey) {
this.warningsIssued.clear();
}
this.currentPeriodKey = periodKey;Good: Warnings are reset when the period rolls over, avoiding duplicate warnings. Note: This works correctly for single BudgetTracker instance but could miss rollovers if multiple instances are created (unlikely in practice). Well-Implemented Features
Test CoverageExcellent: 462 tests in budget-tracker.test.ts, 426 in config.test.ts Minor gap: No tests for concurrent BudgetTracker instances (unlikely in practice) Recommendations SummaryHigh Priority (fix before merge):
Medium Priority (post-merge follow-ups): Low Priority: OverallRecommendation: Approve with critical database cleanup fix The code is production-quality with one critical bug (database connection leak) that should be fixed before merge. The feature is well-designed, thoroughly tested, and follows project conventions. |
|
Author identity unknown *** Please tell me who you are. Run git config --global user.email "you@example.com" to set your account's default identity. fatal: empty ident name (for runner@runnervmkj6or.sqh55rn4vktezahojetgrejhff.gx.internal.cloudapp.net) not allowed |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/config.ts`:
- Around line 268-298: The setBudgetConfig method currently validates
monthlyLimit, perSessionDefault, and warningThresholds but misses validating
e2bHourlyRate; update setBudgetConfig to check if config.e2bHourlyRate is not
undefined/null and throw an Error if it is negative (e.g., "e2bHourlyRate must
be a non-negative number"), then merge into this.config.budget as before and
call this.save(); reference the setBudgetConfig function, the e2bHourlyRate
property on BudgetConfig, and the this.config.budget merge to locate where to
add the validation.
🧹 Nitpick comments (3)
src/config.ts (2)
25-32: Consider renaming localConfiginterface to avoid confusion.This file defines a local
Configinterface (lines 25-28) that differs from theConfiginterface insrc/types.ts(which hasdbPath,staleThresholdMinutes, etc.). While the local interface is not exported, it could cause confusion during maintenance.Consider renaming to
PersistentConfigorUserConfigto clarify its purpose:-interface Config { +interface PersistentConfig { budget: BudgetConfig; [key: string]: unknown; } -const DEFAULT_CONFIG: Config = { +const DEFAULT_CONFIG: PersistentConfig = { budget: DEFAULT_BUDGET_CONFIG };
109-112: Silent error handling may hide permission or I/O issues.The catch block returns defaults without logging the error. While this provides graceful degradation, it could mask permission errors or disk issues that users should be aware of.
Consider logging a warning:
} catch { - // Invalid JSON - return defaults + // Invalid JSON or read error - return defaults + // Note: Could log warning here if logger is available return structuredClone(DEFAULT_CONFIG); }Alternatively, if a logger becomes available to this module, consider logging the actual error at debug/warn level.
src/budget-tracker.ts (1)
172-178:recordSessionCostrelies on DB layer for session validation.The method calls
this.db.updateSessionCost(sessionId, cost)without checking if the session exists. If the session ID is invalid, the behavior depends on the DB implementation. Based on the relevant code snippets,updateSessionCostreturns a boolean indicating success, which is currently ignored.Consider handling the case where the session doesn't exist:
recordSessionCost(sessionId: string, cost: number): void { // Update session cost estimate - this.db.updateSessionCost(sessionId, cost); + const updated = this.db.updateSessionCost(sessionId, cost); + if (!updated) { + throw new Error(`Session ${sessionId} not found`); + } // Update monthly tracking this.recordCost(cost, 'monthly'); }
1. Fix database connection leak in budget-status command - Changed from try/catch to try/catch/finally pattern - Database connection now closes even when errors occur - Prevents connection exhaustion from repeated failed CLI calls 2. Add key validation to ConfigManager.set() - Prevent prototype pollution attacks (__proto__, constructor, prototype) - Reject keys starting with underscore (reserved for internal use) - Validate key format (must start with letter, alphanumeric/hyphen/underscore) - Also added validation to delete() method Added 5 new tests for key validation. https://claude.ai/code/session_01CDYWMZJdg8FH55bgQzNmds
Code Review: Budget Tracking and Enforcement (v1.1)Overall AssessmentThis is a well-implemented feature that adds comprehensive budget tracking capabilities to parallel-cc. The code architecture is clean, follows existing patterns, and includes extensive test coverage (462 budget tracker tests + 426 config tests). ✅ Strengths
🔍 Issues Not Previously Covered1. **Missing Validation for in ** (Medium)The method validates , , and , but does not validate . A negative hourly rate would produce incorrect cost calculations. Location: Suggestion: 2. NaN Validation Missing in Warning Thresholds Parsing (Low)The command parses warning thresholds but doesn't validate for values: Location: If is not a valid number (e.g., 'abc'), returns , which corrupts the config. Suggestion: 3. Division by Zero Risk in Budget Status Display (Low)If is , the division produces : Location: Suggestion: 4. Shared Array Reference in Default Config (Low)The array is spread by reference, allowing mutations to affect the default: Location: Suggestion: Use instead of spreading to prevent shared references. 5. Limited Kebab-to-CamelCase Normalization (Low)The key normalization only handles three specific keys: Location: Suggestion: Use a general regex for maintainability: ✅ Issues Previously AddressedThe following issues from earlier reviews have been resolved:
📊 Test CoverageExcellent: 888 tests for new budget and config features (462 + 426) Minor gaps:
🎯 Recommendations SummaryMedium Priority:
Low Priority: OverallRecommendation: ✅ Approve with minor follow-ups This is production-quality code with comprehensive features and excellent test coverage. The architecture is sound, and the implementation follows project conventions well. The issues identified are low-priority edge cases that don't block the feature's core functionality. The budget tracking system will be a valuable addition for users managing E2B sandbox costs. |
Code Review: Budget Tracking and Enforcement (v1.1)Overall AssessmentThis is a well-implemented feature that adds comprehensive budget tracking capabilities to parallel-cc. The code architecture is clean, follows existing patterns, and includes extensive test coverage (462 budget tracker tests + 426 config tests). ✅ Strengths
🔍 Issues Not Previously Covered1. Missing Validation for
|
|
Author identity unknown *** Please tell me who you are. Run git config --global user.email "you@example.com" to set your account's default identity. fatal: empty ident name (for runner@runnervmkj6or.jt45wvszuagurc5v01ivo3tf0d.bx.internal.cloudapp.net) not allowed |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/cli.ts`:
- Around line 3173-3191: Validate options.period (from the CLI
.option('--period')) against an allow-list ['daily','weekly','monthly'] before
calling BudgetTracker.generateBudgetStatus; if the value is not one of these,
print a clear error message indicating accepted values and exit with a non-zero
status (or throw), and only cast/narrow to 'daily'|'weekly'|'monthly' after the
check so generateBudgetStatus receives a guaranteed valid period.
- Around line 3032-3084: The config set handler currently calls
ConfigManager.set directly and bypasses budget validation; update the action for
the 'set' command to detect budget keys (e.g., normalizedKey includes 'budget'
or specific keys like 'budget.monthlyLimit', 'budget.perSessionDefault',
'budget.warningThresholds') and call the dedicated setBudgetConfig method
instead of configManager.set for those keys; before calling setBudgetConfig,
validate parsedValue (ensure numbers are finite and non-negative for
single-number budget fields, and for arrays like warningThresholds ensure each
parsed element is a finite non-negative number) and throw a descriptive Error on
invalid input so existing catch/JSON output remains correct; for non-budget keys
keep calling ConfigManager.set(normalizedKey, parsedValue) as before.
🧹 Nitpick comments (2)
src/config.ts (1)
96-112: Consider persisting defaults when config JSON is invalid.
Returning defaults without rewriting leaves a corrupted file that will fail on every load. Persisting defaults once avoids repeated parse failures.♻️ Suggested tweak
- } catch { - // Invalid JSON - return defaults - return structuredClone(DEFAULT_CONFIG); - } + } catch { + // Invalid JSON - reset to defaults + const defaults = structuredClone(DEFAULT_CONFIG); + this.save(defaults); + return defaults; + }src/cli.ts (1)
3171-3172: Prefer abudget statussubcommand overbudget-status.
Consider adding abudgetcommand with astatussubcommand (and optionally keepbudget-statusas an alias) to align with CLI conventions.As per coding guidelines: Prefer proper subcommands over hyphenated command names in CLI (e.g.,
parallel-cc templates listinstead ofparallel-cc templates-list).
Fixes from PR review comments: config.ts: - Use structuredClone for DEFAULT_BUDGET_CONFIG to prevent shared refs - Add try/catch in flushSync for ENOENT handling - Add e2bHourlyRate validation in setBudgetConfig (non-negative) cli.ts: - Add NaN validation for warning threshold parsing - Add type checks before toFixed() calls (typeof === 'number') - Validate --period option against allowed values - Guard against division by zero in budget status display
Code Review: Budget Tracking and Enforcement (v1.1)Overall AssessmentThis PR adds comprehensive budget tracking capabilities to parallel-cc. The implementation is well-architected with proper separation of concerns, and most issues from previous reviews have been addressed. However, there is one medium-priority validation bypass that should be fixed. ✅ Issues Previously AddressedThe following issues from earlier reviews have been successfully fixed in commits 4c9ced3 and 8acc228:
🔴 Medium Priority IssueBudget Keys Bypass
|
|
Posted comprehensive PR review identifying one medium-priority validation bypass issue where budget keys skip |
Budget keys set via 'config set budget.*' were bypassing validation in setBudgetConfig(). Now routes all budget.* keys through the validated method to ensure non-negative limits and valid thresholds.
When JSON parsing fails, create a timestamped backup of the corrupted config file before returning defaults. This prevents silent data loss when the user's config has a syntax error.
Code Review: Budget Tracking and Enforcement (v1.1)Overall AssessmentThis PR adds comprehensive budget tracking capabilities to parallel-cc. The implementation is well-architected, follows project conventions, and includes extensive test coverage. Most issues from previous reviews have been addressed, with the validation bypass now fixed in commit f18f53f. ✅ Issues Previously AddressedAll critical and medium-priority issues from prior reviews have been resolved:
🔍 New Observations1. Limited Kebab-to-CamelCase Normalization (Low Priority)Location: The key normalization only handles three specific keys: const normalizedKey = key.replace('monthly-limit', 'monthlyLimit')
.replace('per-session-default', 'perSessionDefault')
.replace('warning-thresholds', 'warningThresholds');This approach is fragile if more budget keys are added. Consider using a general regex: const normalizedKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());2. Non-Budget Numeric Parsing Allows
|
|
I posted a comprehensive review comment for PR #52. Summary: The PR is in excellent shape. All critical and medium-priority issues from previous reviews have been addressed. The implementation is production-ready with comprehensive test coverage (1,270 tests). Key findings:
New low-priority observations:
Recommendation: ✅ Approve The feature is well-tested, secure, and ready for merge. The low-priority items can be addressed in future iterations. |
| if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { | ||
| throw err; | ||
| } | ||
| } |
There was a problem hiding this comment.
🟡 Medium
Consider using atomic writes (write to temp file, then rename) to prevent config loss if the process crashes mid-write. Currently, a partial write followed by restart would trigger the corruption recovery path, losing valid settings.
🚀 Want me to fix this? Reply ex: "fix it for me".
| // Parse value based on key type | ||
| let parsedValue: unknown; | ||
|
|
||
| if (normalizedKey.includes('budget')) { |
There was a problem hiding this comment.
🟡 Medium
Budget config key handling is too permissive and can misroute or overwrite settings. Suggest: only treat keys that start with budget. as budget config (use normalizedKey.startsWith('budget.') for parsing and routing), reject the bare budget key to avoid replacing the whole object, and pass other keys to set().
| if (normalizedKey.includes('budget')) { | |
| if (normalizedKey.startsWith('budget.')) { |
🚀 Want me to fix this? Reply ex: "fix it for me".
| this.ensureDirectory(); | ||
| this.config = this.load(); | ||
| } | ||
|
|
There was a problem hiding this comment.
🟠 High
The constructor calls load(), which calls save() for missing files, scheduling an async flushSync() via setTimeout. If that write fails, the error is re-thrown inside the timer callback and cannot be caught by callers of new ConfigManager(). Consider either writing synchronously in load() or catching/logging errors in the timer callback.
🚀 Want me to fix this? Reply ex: "fix it for me".
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/cli.ts`:
- Around line 3040-3043: The key normalization logic that builds normalizedKey
(the replace chain around 'monthly-limit', 'per-session-default',
'warning-thresholds') must also map 'e2b-hourly-rate' to 'e2bHourlyRate' so
setting budget.e2b-hourly-rate updates the e2bHourlyRate field; update the three
equivalent spots that perform this normalization (the occurrence creating
normalizedKey near the shown block and the repeats referenced at the other two
locations) to include .replace('e2b-hourly-rate', 'e2bHourlyRate').
Additionally, ensure the config listing/output code that displays budget fields
(the same CLI list format logic referenced around lines 3105-3108 and 3147-3153)
includes and prints the e2bHourlyRate value so users can confirm the hourly rate
after setting it.
In `@src/config.ts`:
- Around line 315-343: The setBudgetConfig validation allows NaN/Infinity
because it only checks < 0; update validation in setBudgetConfig (BudgetConfig)
to use Number.isFinite guards for numeric fields (monthlyLimit,
perSessionDefault, e2bHourlyRate) before performing range checks and throw the
same errors if not finite, and for warningThresholds ensure each threshold is
Number.isFinite and between 0 and 1 (inclusive) before accepting it; this
prevents NaN/Infinity from bypassing checks and corrupting budget calculations.
🧹 Nitpick comments (1)
src/cli.ts (1)
3181-3184: Prefer abudget statussubcommand instead ofbudget-status.This keeps CLI naming consistent with the subcommand convention used elsewhere.
♻️ Possible restructuring
-program - .command('budget-status') - .description('Show budget and spending status (v1.1)') +const budgetCmd = program + .command('budget') + .description('Budget commands (v1.1)'); + +budgetCmd + .command('status') + .description('Show budget and spending status')As per coding guidelines: Prefer proper subcommands over hyphenated command names in CLI (e.g.,
parallel-cc templates listinstead ofparallel-cc templates-list).
| // Convert key from kebab-case to camelCase for budget keys | ||
| const normalizedKey = key.replace('monthly-limit', 'monthlyLimit') | ||
| .replace('per-session-default', 'perSessionDefault') | ||
| .replace('warning-thresholds', 'warningThresholds'); |
There was a problem hiding this comment.
Normalize e2b-hourly-rate and surface it in config list.
Right now only monthly/per-session/thresholds are normalized, so config set budget.e2b-hourly-rate 0.12 won’t update the actual e2bHourlyRate field. Also, list output doesn’t show the hourly rate, making it hard to confirm.
🛠️ Suggested CLI normalization + list output
- const normalizedKey = key.replace('monthly-limit', 'monthlyLimit')
- .replace('per-session-default', 'perSessionDefault')
- .replace('warning-thresholds', 'warningThresholds');
+ const normalizedKey = key.replace('monthly-limit', 'monthlyLimit')
+ .replace('per-session-default', 'perSessionDefault')
+ .replace('warning-thresholds', 'warningThresholds')
+ .replace('e2b-hourly-rate', 'e2bHourlyRate');- const normalizedKey = key.replace('monthly-limit', 'monthlyLimit')
- .replace('per-session-default', 'perSessionDefault')
- .replace('warning-thresholds', 'warningThresholds');
+ const normalizedKey = key.replace('monthly-limit', 'monthlyLimit')
+ .replace('per-session-default', 'perSessionDefault')
+ .replace('warning-thresholds', 'warningThresholds')
+ .replace('e2b-hourly-rate', 'e2bHourlyRate'); console.log(` monthly-limit: ${typeof budget.monthlyLimit === 'number' ? `$${budget.monthlyLimit.toFixed(2)}` : chalk.dim('(not set)')}`);
console.log(` per-session-default: ${typeof budget.perSessionDefault === 'number' ? `$${budget.perSessionDefault.toFixed(2)}` : chalk.dim('(not set)')}`);
console.log(` warning-thresholds: ${Array.isArray(budget.warningThresholds) ? budget.warningThresholds.map(t => `${(t * 100).toFixed(0)}%`).join(', ') : chalk.dim('(not set)')}`);
+ console.log(` e2b-hourly-rate: ${typeof budget.e2bHourlyRate === 'number' ? `$${budget.e2bHourlyRate.toFixed(2)}/hr` : chalk.dim('(not set)')}`);Also applies to: 3105-3108, 3147-3153
🤖 Prompt for AI Agents
In `@src/cli.ts` around lines 3040 - 3043, The key normalization logic that builds
normalizedKey (the replace chain around 'monthly-limit', 'per-session-default',
'warning-thresholds') must also map 'e2b-hourly-rate' to 'e2bHourlyRate' so
setting budget.e2b-hourly-rate updates the e2bHourlyRate field; update the three
equivalent spots that perform this normalization (the occurrence creating
normalizedKey near the shown block and the repeats referenced at the other two
locations) to include .replace('e2b-hourly-rate', 'e2bHourlyRate').
Additionally, ensure the config listing/output code that displays budget fields
(the same CLI list format logic referenced around lines 3105-3108 and 3147-3153)
includes and prints the e2bHourlyRate value so users can confirm the hourly rate
after setting it.
| setBudgetConfig(config: Partial<BudgetConfig>): void { | ||
| // Validate budget limits | ||
| if (config.monthlyLimit !== undefined && config.monthlyLimit !== null) { | ||
| if (config.monthlyLimit < 0) { | ||
| throw new Error('Budget limit must be a positive number'); | ||
| } | ||
| } | ||
|
|
||
| if (config.perSessionDefault !== undefined && config.perSessionDefault !== null) { | ||
| if (config.perSessionDefault < 0) { | ||
| throw new Error('Per-session budget must be a positive number'); | ||
| } | ||
| } | ||
|
|
||
| // Validate warning thresholds | ||
| if (config.warningThresholds !== undefined) { | ||
| for (const threshold of config.warningThresholds) { | ||
| if (threshold < 0 || threshold > 1) { | ||
| throw new Error('Warning thresholds must be between 0 and 1'); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Validate e2bHourlyRate | ||
| if (config.e2bHourlyRate !== undefined && config.e2bHourlyRate !== null) { | ||
| if (config.e2bHourlyRate < 0) { | ||
| throw new Error('E2B hourly rate must be a non-negative number'); | ||
| } | ||
| } |
There was a problem hiding this comment.
Harden budget validation against NaN/Infinity.
setBudgetConfig only checks < 0, so NaN/Infinity can slip in and poison budget calculations. Consider Number.isFinite guards for numeric fields and thresholds.
🛡️ Proposed validation hardening
- if (config.monthlyLimit !== undefined && config.monthlyLimit !== null) {
- if (config.monthlyLimit < 0) {
+ if (config.monthlyLimit !== undefined && config.monthlyLimit !== null) {
+ if (!Number.isFinite(config.monthlyLimit) || config.monthlyLimit < 0) {
throw new Error('Budget limit must be a positive number');
}
}
- if (config.perSessionDefault !== undefined && config.perSessionDefault !== null) {
- if (config.perSessionDefault < 0) {
+ if (config.perSessionDefault !== undefined && config.perSessionDefault !== null) {
+ if (!Number.isFinite(config.perSessionDefault) || config.perSessionDefault < 0) {
throw new Error('Per-session budget must be a positive number');
}
}
// Validate warning thresholds
if (config.warningThresholds !== undefined) {
for (const threshold of config.warningThresholds) {
- if (threshold < 0 || threshold > 1) {
+ if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
throw new Error('Warning thresholds must be between 0 and 1');
}
}
}
// Validate e2bHourlyRate
if (config.e2bHourlyRate !== undefined && config.e2bHourlyRate !== null) {
- if (config.e2bHourlyRate < 0) {
+ if (!Number.isFinite(config.e2bHourlyRate) || config.e2bHourlyRate < 0) {
throw new Error('E2B hourly rate must be a non-negative number');
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setBudgetConfig(config: Partial<BudgetConfig>): void { | |
| // Validate budget limits | |
| if (config.monthlyLimit !== undefined && config.monthlyLimit !== null) { | |
| if (config.monthlyLimit < 0) { | |
| throw new Error('Budget limit must be a positive number'); | |
| } | |
| } | |
| if (config.perSessionDefault !== undefined && config.perSessionDefault !== null) { | |
| if (config.perSessionDefault < 0) { | |
| throw new Error('Per-session budget must be a positive number'); | |
| } | |
| } | |
| // Validate warning thresholds | |
| if (config.warningThresholds !== undefined) { | |
| for (const threshold of config.warningThresholds) { | |
| if (threshold < 0 || threshold > 1) { | |
| throw new Error('Warning thresholds must be between 0 and 1'); | |
| } | |
| } | |
| } | |
| // Validate e2bHourlyRate | |
| if (config.e2bHourlyRate !== undefined && config.e2bHourlyRate !== null) { | |
| if (config.e2bHourlyRate < 0) { | |
| throw new Error('E2B hourly rate must be a non-negative number'); | |
| } | |
| } | |
| setBudgetConfig(config: Partial<BudgetConfig>): void { | |
| // Validate budget limits | |
| if (config.monthlyLimit !== undefined && config.monthlyLimit !== null) { | |
| if (!Number.isFinite(config.monthlyLimit) || config.monthlyLimit < 0) { | |
| throw new Error('Budget limit must be a positive number'); | |
| } | |
| } | |
| if (config.perSessionDefault !== undefined && config.perSessionDefault !== null) { | |
| if (!Number.isFinite(config.perSessionDefault) || config.perSessionDefault < 0) { | |
| throw new Error('Per-session budget must be a positive number'); | |
| } | |
| } | |
| // Validate warning thresholds | |
| if (config.warningThresholds !== undefined) { | |
| for (const threshold of config.warningThresholds) { | |
| if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) { | |
| throw new Error('Warning thresholds must be between 0 and 1'); | |
| } | |
| } | |
| } | |
| // Validate e2bHourlyRate | |
| if (config.e2bHourlyRate !== undefined && config.e2bHourlyRate !== null) { | |
| if (!Number.isFinite(config.e2bHourlyRate) || config.e2bHourlyRate < 0) { | |
| throw new Error('E2B hourly rate must be a non-negative number'); | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@src/config.ts` around lines 315 - 343, The setBudgetConfig validation allows
NaN/Infinity because it only checks < 0; update validation in setBudgetConfig
(BudgetConfig) to use Number.isFinite guards for numeric fields (monthlyLimit,
perSessionDefault, e2bHourlyRate) before performing range checks and throw the
same errors if not finite, and for warningThresholds ensure each threshold is
Number.isFinite and between 0 and 1 (inclusive) before accepting it; this
prevents NaN/Infinity from bypassing checks and corrupting budget calculations.
Per project guidelines, prefer proper subcommands over hyphenated names. - Created 'budget' command group with 'status' subcommand - Kept 'budget-status' as alias for backward compatibility - Updated CLAUDE.md CLI Commands table with budget and config commands
Code Review: Budget Tracking and Enforcement (v1.1)Overall AssessmentThis PR adds comprehensive budget tracking capabilities to parallel-cc. The implementation is well-architected, follows project conventions, and includes extensive test coverage (1,270 tests). All critical and medium-priority issues from previous reviews have been successfully addressed. ✅ Issues Previously ResolvedThe following issues identified in earlier reviews have been fixed:
🔍 Remaining Low-Priority Observations1. Missing
|
|
Review comment posted successfully. The PR is approved with all critical and medium-priority issues resolved. Five low-priority observations identified for future follow-ups. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/cli.ts`:
- Around line 1755-1765: The budget validation currently allows zero; update the
check around options.budget so budgetAmount must be greater than 0 (reject
budgetAmount <= 0) instead of only rejecting negative values, and throw a clear
error like `Invalid budget amount: ... Must be a positive number greater than
0.` Ensure this change is applied where budgetAmount is parsed and before
calling sandboxManager.setBudgetLimit(sandboxId, budgetAmount).




Summary
This PR introduces comprehensive budget tracking and enforcement capabilities to parallel-cc, allowing users to set spending limits on sandbox sessions and monitor costs across different time periods (daily, weekly, monthly).
Key Changes
New Modules
src/budget-tracker.ts: Core budget tracking system with features for:src/config.ts: Persistent configuration management system:~/.parallel-cc/config.jsonbudget.monthlyLimit)Database Enhancements (
src/db.ts)budget_trackingtable support with methods to:cost_estimateandactual_costfieldsSandbox Budget Enforcement (
src/e2b/sandbox-manager.ts)BudgetExceededErrorexception for budget violationsCLI Commands
config set/get/list: Manage configuration valuesparallel-cc config set budget.monthly-limit 10.00parallel-cc config get budget.monthly-limitparallel-cc config listbudget-status: Display spending and budget informationruncommand enhancement: New--budgetoption to set per-session limitsparallel-cc run --budget 0.50limits session to $0.50Type System Updates (
src/types.ts)BudgetConfig: User configuration for budget settingsBudgetStatus: Report structure for budget informationBudgetWarning: Warning details during executionBudgetExceededError: Custom error class for budget violationsBudgetPeriod: Type for period selection (daily/weekly/monthly)Execution Monitoring (
src/e2b/claude-runner.ts)Implementation Details
Testing Considerations
https://claude.ai/code/session_01CDYWMZJdg8FH55bgQzNmds
Summary by CodeRabbit
New Features
CLI
Tests
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.