Skip to content

Add budget tracking and enforcement for sandbox sessions (v1.1)#52

Merged
frankbria merged 7 commits intomainfrom
claude/add-budget-limits-eMu6m
Jan 30, 2026
Merged

Add budget tracking and enforcement for sandbox sessions (v1.1)#52
frankbria merged 7 commits intomainfrom
claude/add-budget-limits-eMu6m

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Jan 29, 2026

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:

    • Period-based spending tracking (daily, weekly, monthly)
    • Budget limit enforcement with configurable warning thresholds
    • Session cost recording and budget status reports
    • Warning threshold management to alert users when spending reaches configured percentages
  • src/config.ts: Persistent configuration management system:

    • JSON file storage in ~/.parallel-cc/config.json
    • Dot notation support for nested keys (e.g., budget.monthlyLimit)
    • Budget configuration validation
    • Automatic directory creation and file persistence

Database Enhancements (src/db.ts)

  • Added budget_tracking table support with methods to:
    • Create and retrieve budget tracking records
    • Update spending amounts
    • Track session costs with cost_estimate and actual_cost fields
    • Query budget history by period

Sandbox Budget Enforcement (src/e2b/sandbox-manager.ts)

  • Per-sandbox budget limit tracking
  • Real-time cost calculation based on elapsed time
  • Soft warnings at configurable thresholds (default: 50%, 80%)
  • Hard limit enforcement that terminates sandboxes when budget is exceeded
  • New BudgetExceededError exception for budget violations

CLI Commands

  • config set/get/list: Manage configuration values

    • Set budget limits: parallel-cc config set budget.monthly-limit 10.00
    • Get specific values: parallel-cc config get budget.monthly-limit
    • List all settings: parallel-cc config list
  • budget-status: Display spending and budget information

    • Show current period spending and remaining budget
    • List active E2B sessions with their costs
    • Support for different periods (daily, weekly, monthly)
    • JSON output option for programmatic use
  • run command enhancement: New --budget option to set per-session limits

    • Example: parallel-cc run --budget 0.50 limits session to $0.50

Type System Updates (src/types.ts)

  • BudgetConfig: User configuration for budget settings
  • BudgetStatus: Report structure for budget information
  • BudgetWarning: Warning details during execution
  • BudgetExceededError: Custom error class for budget violations
  • BudgetPeriod: Type for period selection (daily/weekly/monthly)

Execution Monitoring (src/e2b/claude-runner.ts)

  • Integrated budget checking into the execution monitoring loop
  • Soft warnings logged but execution continues
  • Hard limit violations trigger sandbox termination

Implementation Details

  • Cost Calculation: Uses E2B's pricing model (~$0.10/hour) for real-time cost estimation
  • Warning Thresholds: Configurable percentages (default: 50%, 80%) to warn before hitting limits
  • Period Calculation: Proper handling of daily (calendar day), weekly (Monday-Sunday), and monthly (1st-last) periods
  • Persistent Storage: Configuration stored in user's home directory, survives across sessions
  • Backward Compatible: Budget features are optional; existing functionality unaffected when not configured

Testing Considerations

  • Budget tracking works with existing session database
  • Configuration persists across CLI invocations
  • Budget enforcement integrates seamlessly with sandbox lifecycle
  • Warning thresholds prevent duplicate warnings for same threshold

https://claude.ai/code/session_01CDYWMZJdg8FH55bgQzNmds

Summary by CodeRabbit

  • New Features

    • Period-based budget tracking (daily/weekly/monthly) with monthly limits, per-session budgets, spending reports, and configurable warning thresholds; soft warnings and hard enforcement that can terminate sessions.
    • Persistent, typed configuration manager with safe on-disk defaults and budget settings; per-session default budgets and hourly-rate support.
  • CLI

    • Added budget-status command, per-session --budget option, and config set/get/list for budget configuration.
  • Tests

    • Extensive tests for budget tracking, config manager, and sandbox budget enforcement.
  • Documentation

    • CLI docs updated with new commands.

✏️ Tip: You can customize this high-level summary in your review settings.

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
@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

Walkthrough

Adds 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

Cohort / File(s) Summary
Budget Tracking Core
src/budget-tracker.ts
New BudgetTracker class and related interfaces for UTC period calculations, period record lifecycle, cost recording (overall and per-session), monthly checks, warning thresholds, session-budget validation, and status generation.
Configuration Management
src/config.ts
New ConfigManager, DEFAULT_BUDGET_CONFIG, and DEFAULT_CONFIG_PATH; persistent JSON config with tilde expansion, dot-notation get/set/delete, debounced persistence, budget validation, and budget-specific getters/setters.
Database Integration
src/db.ts
SessionDB extended with budget-tracking APIs and helpers: table detection, create/get/getOrCreate budget records, updateBudgetSpent, updateSessionCost, getBudgetTrackingHistory, and row->model conversion; budget types re-exported.
Runtime Sandbox Enforcement
src/e2b/sandbox-manager.ts
SandboxManager extended with ExtendedE2BSessionConfig, per-sandbox budgetLimits and budgetWarningsIssued, methods setBudgetLimit/getBudgetLimit/checkBudgetLimit, numeric cost calc helper, soft warnings and hard BudgetExceededError termination, and cleanup on termination/error.
Execution Flow Integration
src/e2b/claude-runner.ts
Adds budget checks into monitorExecution: logs soft warnings, enforces hard termination on BudgetExceededError, and surfaces budget outcomes to execution control flow.
CLI Commands & Options
src/cli.ts
Imports ConfigManager, DEFAULT_CONFIG_PATH, and BudgetTracker; adds config subcommands (set/get/list), budget-status command/alias, and per-session --budget flag for sandbox runs with validation and JSON/human outputs.
Type System Definitions
src/types.ts
New/updated types and error: BudgetConfig, BudgetStatus, BudgetWarning, BudgetExceededError, and E2BSessionConfig now includes budgetWarningThresholds?; public types expanded.
Tests
tests/budget-tracker.test.ts, tests/config.test.ts, tests/e2b/budget-enforcement.test.ts
Extensive tests covering period math, record lifecycle, cost recording, session budgeting/validation, warning thresholds, config persistence/validation, and sandbox budget enforcement (soft/hard, cleanup, thresholds).
Docs / CLI Table
CLAUDE.md
Adds CLI command entries for config set/get/list and budget status/budget-status alias.

Sequence Diagrams

sequenceDiagram
    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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐇 I hopped through configs, numbers bright,

I marked the months and logged the night.
Warnings whisper at fifty and eighty,
Sessions counted, coins tucked stately.
Hooray — tidy ledgers in the burrowed light!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding budget tracking and enforcement for sandbox sessions, which aligns with the substantial additions across multiple files (budget-tracker.ts, config.ts, database methods, sandbox enforcement, CLI commands).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/add-budget-limits-eMu6m

Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp
Copy link
Contributor

macroscopeapp bot commented Jan 29, 2026

Add budget tracking and enforcement for sandbox sessions by terminating over-budget runs in SandboxManager.checkBudgetLimit and issuing warnings at 50% and 80% usage

Introduce period-based budget tracking, per-session limits, and CLI controls; enforce budgets during execution in e2b/claude-runner.ts via SandboxManager.checkBudgetLimit, persist config with ConfigManager, and expose status via budget status CLI. See src/e2b/sandbox-manager.ts, src/e2b/claude-runner.ts, src/budget-tracker.ts, src/config.ts, and src/cli.ts.

📍Where to Start

Start with budget enforcement in monitorExecution using SandboxManager.checkBudgetLimit in src/e2b/claude-runner.ts, then review budget limit logic in src/e2b/sandbox-manager.ts.


Macroscope summarized c3a8ddf.

@github-actions
Copy link

Code Review: Budget Tracking and Enforcement (v1.1)

✅ Overall Assessment

This 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

  1. Clean Architecture: Budget tracking, config, and database layers are properly separated
  2. Type Safety: Strong TypeScript usage throughout with proper type definitions
  3. Backward Compatibility: Budget features are optional and don't break existing functionality
  4. Comprehensive Tests: 462 budget tracker tests + 426 config tests = 888 tests for new features
  5. Documentation: Well-documented with clear JSDoc comments

⚠️ Issues & Concerns

1. Potential Precision Bug (budget-tracker.ts:59,274)

private warningsIssued: Set<number> = new Set();
// ...
if (percentUsed >= threshold && !this.warningsIssued.has(threshold)) {
  this.warningsIssued.add(threshold);

Issue: Using floats (0.5, 0.8) as Set keys can have precision comparison issues.
Suggestion: Consider using integer percentages (50, 80) or string keys for reliable lookups.

2. Hard-coded Pricing (sandbox-manager.ts:470)

const costPerMinute = 0.10 / 60; // Hard-coded $0.10/hour

Issue: E2B pricing may change. This hard-coded value will become outdated.
Suggestion: Move to config or fetch from E2B SDK if available.

3. Synchronous File I/O (config.ts:114-116)

private save(config?: Config): void {
  const toSave = config ?? this.config;
  writeFileSync(this.configPath, JSON.stringify(toSave, null, 2));
}

Issue: Every set() operation writes to disk synchronously, which could block during rapid configuration changes.
Suggestion: Consider debouncing or async writes for better performance under high load.

4. Period Calculation Edge Case (budget-tracker.ts:73-96)

getPeriodStart(period: BudgetPeriod, date: Date = new Date()): string {
  const year = date.getUTCFullYear();
  const month = date.getUTCMonth();
  // ...
}

Issue: Using UTC dates may not match user's local calendar. Users in different timezones may see different "weekly" periods.
Suggestion: Consider making timezone configurable or documenting that periods are UTC-based.

5. Budget Reset Timing (budget-tracker.ts:342-344)

resetWarnings(): void {
  this.warningsIssued.clear();
}

Issue: Warning reset is never called in the codebase. Warnings persist across periods.
Suggestion: Consider resetting warnings when period rolls over or adding reset logic to getOrCreatePeriodRecord.

🔍 Performance Considerations

  • Database queries are efficient with proper indexes on budget_tracking table
  • Budget checks run in monitoring loop (reasonable overhead)
  • Config writes are synchronous - only a concern if rapid config changes occur (unlikely in practice)

🛡️ Security Review

Positive findings:

  • Input validation for negative budgets and invalid thresholds
  • No injection vulnerabilities detected
  • Budget enforcement prevents runaway spending

📊 Test Coverage

Excellent coverage:

  • 462 tests in budget-tracker.test.ts
  • 426 tests in config.ts
  • All edge cases covered: period boundaries, negative values, accumulation, etc.

Minor gap:

  • No tests for concurrent BudgetTracker instances (though unlikely in practice)

🎯 Recommendations Summary

High Priority:

  1. Fix float-as-Set-key issue (potential precision bugs)
  2. Document or make pricing configurable

Medium Priority:
3. Consider timezone handling for periods
4. Implement warning reset on period rollover

Low Priority:
5. Debounce config writes (only if performance issues arise)

📝 Code Quality Notes

  • Follows project coding standards
  • Consistent with existing codebase patterns
  • Good error handling throughout
  • Clear separation of concerns

Overall: This is production-ready code with minor edge case concerns. The feature set is comprehensive and well-tested.

Recommendation: ✅ Approve with minor follow-ups for the float precision issue and pricing configuration.

@github-actions
Copy link

I've posted a comprehensive review of PR #52 covering code quality, potential bugs, performance considerations, security concerns, and test coverage. The review highlights:

  • Overall positive assessment - well-implemented feature with clean architecture
  • 5 specific issues to address:
    1. Float precision in Set keys (medium priority)
    2. Hard-coded pricing (medium priority)
    3. Synchronous file I/O (low priority)
    4. UTC-based period calculation (medium priority)
    5. Missing warning reset logic (medium priority)
  • Security is solid with proper input validation
  • Test coverage is excellent at 888 tests for new features

The recommendation is to approve with minor follow-ups for the float precision issue and pricing configuration.

New%20session%20-%202026-01-29T23%3A06%3A33.124Z
opencode session  |  github run

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 internal Config interface to avoid confusion.

The local Config interface here has the same name as the exported Config interface in src/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 in getOrCreateBudgetTrackingRecord.

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 IGNORE with a unique constraint, or wrapping in a transaction.

src/budget-tracker.ts (1)

145-151: Consider verifying session existence before recording cost.

recordSessionCost calls db.updateSessionCost() which may silently fail if the session ID doesn't exist (returns false). 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 using instanceof for type-safe error checking.

The budget check uses error.name === 'BudgetExceededError' for identification. While this works, using instanceof would be more type-safe and would catch subclasses. However, BudgetExceededError would 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 instanceof fails.

💡 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
@github-actions
Copy link

Code Review: Budget Tracking and Enforcement (v1.1)

Overall Assessment

This 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 Issues

1. 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 Issues

2. Potential Type Violation in Sandbox Budget Warnings ()

Issue: The check uses a float comparison, but the Set key is an integer. This means:

  • If and , the comparison passes, is added to Set
  • But 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 ()

Issues:

  1. No validation of dangerous keys (Macroscope noted this)
  2. creates plain objects, breaking potential subclass types
  3. No type validation for parameter - anything can be stored

Recommendation: Add key validation and type checking for budget fields.


🟢 Low Priority / Observations

4. 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

  1. Integer Percentage Set Keys: Using in budget-tracker.ts:309 avoids float precision issues (this was noted in a previous review but the fix is correct)

  2. Debounced Config Writes: 100ms debounce in config.ts:52 prevents excessive I/O

  3. Period Rollover Detection: Automatic warning reset on period boundary

  4. Configurable Pricing: allows updating E2B pricing without code changes

  5. Comprehensive Error Messages: Clear user-facing messages for budget exceeded errors


📊 Test Coverage

Excellent: 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 Summary

High Priority (fix before merge):

  1. ✅ Add finally block to ensure database closure in budget-status command

Medium Priority (post-merge follow-ups):
2. Document the precision behavior of budget warning thresholds
3. Add key validation to ConfigManager.set() (dangerous keys)
4. Consider adding type validation for budget config values

Low Priority:
5. Document that multiple BudgetTracker instances might not share warning state


Overall

Recommendation: ✅ 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.

@github-actions
Copy link

Code Review: Budget Tracking and Enforcement (v1.1)

Overall Assessment

This 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 Issues

1. 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 Issues

2. 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:

  1. No validation of dangerous keys (Macroscope noted this)
  2. Creates plain objects, breaking potential subclass types
  3. No type validation for value parameter

Recommendation: Add key validation and type checking for budget fields.


Low Priority / Observations

4. 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

  1. Integer Percentage Set Keys: Using Math.round(threshold * 100) in budget-tracker.ts:309 avoids float precision issues

  2. Debounced Config Writes: 100ms debounce in config.ts:52 prevents excessive I/O

  3. Period Rollover Detection: Automatic warning reset on period boundary

  4. Configurable Pricing: e2bHourlyRate allows updating E2B pricing without code changes

  5. Comprehensive Error Messages: Clear user-facing messages for budget exceeded errors


Test Coverage

Excellent: 462 tests in budget-tracker.test.ts, 426 in config.test.ts

Minor gap: No tests for concurrent BudgetTracker instances (unlikely in practice)


Recommendations Summary

High Priority (fix before merge):

  1. Add finally block to ensure database closure in budget-status command

Medium Priority (post-merge follow-ups):
2. Document the precision behavior of budget warning thresholds
3. Add key validation to ConfigManager.set() (dangerous keys)
4. Consider adding type validation for budget config values

Low Priority:
5. Document that multiple BudgetTracker instances might not share warning state


Overall

Recommendation: 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.

@github-actions
Copy link

Author identity unknown

*** Please tell me who you are.

Run

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: empty ident name (for runner@runnervmkj6or.sqh55rn4vktezahojetgrejhff.gx.internal.cloudapp.net) not allowed

opencode session  |  github run

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 local Config interface to avoid confusion.

This file defines a local Config interface (lines 25-28) that differs from the Config interface in src/types.ts (which has dbPath, staleThresholdMinutes, etc.). While the local interface is not exported, it could cause confusion during maintenance.

Consider renaming to PersistentConfig or UserConfig to 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: recordSessionCost relies 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, updateSessionCost returns 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
@github-actions
Copy link

Code Review: Budget Tracking and Enforcement (v1.1)

Overall Assessment

This 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

  1. Excellent Architecture: Proper separation of concerns between budget-tracker, config, and database layers
  2. Backward Compatibility: Budget features are optional and don't affect existing functionality
  3. Comprehensive Testing: 888 tests covering edge cases, period boundaries, and validation
  4. Security Hardening: Config key validation prevents prototype pollution attacks
  5. Documentation: Clear JSDoc comments with important notes about UTC-based period calculations

🔍 Issues Not Previously Covered

1. **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:
Invalid threshold value in:


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 Addressed

The following issues from earlier reviews have been resolved:

  • ✅ Database connection leak fixed (finally block added in command)
  • ✅ Float precision in Set keys fixed (integer percentages used)
  • ✅ Period rollover warning reset implemented
  • ✅ Config key validation added (prevents prototype pollution)
  • ✅ E2B pricing made configurable via

📊 Test Coverage

Excellent: 888 tests for new budget and config features (462 + 426)

Minor gaps:

  • No tests for concurrent instances (unlikely in practice)
  • No tests for handling in config CLI parsing

🎯 Recommendations Summary

Medium Priority:

  1. ✅ Add validation in

Low Priority:
2. Add validation in warning thresholds parsing
3. Guard against division by zero in budget status
4. Use for DEFAULT_BUDGET_CONFIG
5. Generalize kebab-to-camelCase normalization


Overall

Recommendation: ✅ 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.

@github-actions
Copy link

Code Review: Budget Tracking and Enforcement (v1.1)

Overall Assessment

This 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

  1. Excellent Architecture: Proper separation of concerns between budget-tracker, config, and database layers
  2. Backward Compatibility: Budget features are optional and don't affect existing functionality
  3. Comprehensive Testing: 888 tests covering edge cases, period boundaries, and validation
  4. Security Hardening: Config key validation prevents prototype pollution attacks
  5. Documentation: Clear JSDoc comments with important notes about UTC-based period calculations

🔍 Issues Not Previously Covered

1. Missing Validation for e2bHourlyRate in setBudgetConfig() (Medium)

The setBudgetConfig() method validates monthlyLimit, perSessionDefault, and warningThresholds, but does not validate e2bHourlyRate. A negative hourly rate would produce incorrect cost calculations.

Location: src/config.ts:303-332

Suggestion:

// After line 324, add:
if (config.e2bHourlyRate !== undefined && config.e2bHourlyRate !== null) {
  if (config.e2bHourlyRate < 0) {
    throw new Error('E2B hourly rate must be a positive number');
  }
}

2. NaN Validation Missing in Warning Thresholds Parsing (Low)

The config set command parses warning thresholds but doesn't validate for NaN values:

Location: src/cli.ts:3052

If v is not a valid number (e.g., 'abc'), parseFloat returns NaN, which corrupts the config.

Suggestion:

const parsed = value.split(',').map(v => parseFloat(v.trim()));
if (parsed.some(v => isNaN(v))) {
  throw new Error(`Invalid threshold value in: ${value}`);
}
parsedValue = parsed;

3. Division by Zero Risk in Budget Status Display (Low)

If status.currentPeriod.limit is 0, the division produces Infinity:

Location: src/cli.ts:3207

Suggestion:

const percentUsed = status.currentPeriod.limit > 0 
  ? (status.currentPeriod.spent / status.currentPeriod.limit) * 100 
  : 0;

4. Shared Array Reference in Default Config (Low)

The DEFAULT_BUDGET_CONFIG array is spread by reference, allowing mutations to affect the default:

Location: src/config.ts:104-106

Suggestion: Use structuredClone(DEFAULT_BUDGET_CONFIG) instead of spreading to prevent shared references.


5. Limited Kebab-to-CamelCase Normalization (Low)

The key normalization only handles three specific keys:

Location: src/cli.ts:3041-3043

Suggestion: Use a general regex for maintainability:

const normalizedKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());

✅ Issues Previously Addressed

The following issues from earlier reviews have been resolved:

  • ✅ Database connection leak fixed (finally block added in budget-status command)
  • ✅ Float precision in Set keys fixed (integer percentages used)
  • ✅ Period rollover warning reset implemented
  • ✅ Config key validation added (prevents prototype pollution)
  • ✅ E2B pricing made configurable via e2bHourlyRate

📊 Test Coverage

Excellent: 888 tests for new budget and config features (462 + 426)

Minor gaps:

  • No tests for concurrent BudgetTracker instances (unlikely in practice)
  • No tests for NaN handling in config CLI parsing

🎯 Recommendations Summary

Medium Priority:

  1. ✅ Add e2bHourlyRate validation in setBudgetConfig()

Low Priority:
2. Add NaN validation in warning thresholds parsing
3. Guard against division by zero in budget status
4. Use structuredClone for DEFAULT_BUDGET_CONFIG
5. Generalize kebab-to-camelCase normalization


Overall

Recommendation: ✅ 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.

@github-actions
Copy link

Author identity unknown

*** Please tell me who you are.

Run

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: empty ident name (for runner@runnervmkj6or.jt45wvszuagurc5v01ivo3tf0d.bx.internal.cloudapp.net) not allowed

opencode session  |  github run

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 a budget status subcommand over budget-status.
Consider adding a budget command with a status subcommand (and optionally keep budget-status as 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 list instead of parallel-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
@github-actions
Copy link

Code Review: Budget Tracking and Enforcement (v1.1)

Overall Assessment

This 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 Addressed

The following issues from earlier reviews have been successfully fixed in commits 4c9ced3 and 8acc228:

  • ✅ Database connection leak fixed (finally block added to budget-status command)
  • ✅ Float precision in Set keys fixed (integer percentages used)
  • ✅ Period rollover warning reset implemented
  • ✅ Config key validation added (prevents prototype pollution)
  • ✅ E2B pricing made configurable via e2bHourlyRate
  • e2bHourlyRate validation added in setBudgetConfig()
  • ✅ NaN validation added in warning thresholds parsing
  • ✅ Division by zero risk in budget status display fixed
  • ✅ Type checks before toFixed() calls added
  • --period option validation against allowed values
  • ✅ Shared array reference in default config fixed (structuredClone)
  • ✅ ENOENT error handling in flushSync()

🔴 Medium Priority Issue

Budget Keys Bypass setBudgetConfig() Validation

The config set command calls configManager.set() directly for budget keys (cli.ts:3072), bypassing important validation in setBudgetConfig():

// cli.ts:3072 - Current implementation
configManager.set(normalizedKey, parsedValue);

The setBudgetConfig() method (config.ts:307-328) provides critical validation:

  • Checks monthlyLimit, perSessionDefault, e2bHourlyRate are non-negative
  • Checks warningThresholds are between 0 and 1

Impact: Users can set negative values or invalid threshold ranges, leading to incorrect budget calculations.

Recommended Fix:

// After line 3070 in cli.ts:
if (normalizedKey.startsWith('budget.')) {
  const budgetKey = normalizedKey.replace(/^budget\./, '');
  const budgetConfig = { [budgetKey]: parsedValue } as Partial<BudgetConfig>;
  configManager.setBudgetConfig(budgetConfig);
} else {
  configManager.set(normalizedKey, parsedValue);
}

This ensures all budget settings route through the validated setBudgetConfig() method.

🟢 Low Priority Observations

1. Limited Kebab-to-CamelCase Normalization (cli.ts:3041-3043)

The normalization only handles three specific keys. If more budget keys are added, this will need updating.

Suggestion: Use a general regex for maintainability:

const normalizedKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());

2. Potential NaN in Non-Budget Numeric Parsing (cli.ts:3066-3067)

Non-budget numeric values use !isNaN(parseFloat(value)) without Number.isFinite() check. While unlikely to cause issues, parseFloat('Infinity') would be accepted.

3. Consider Verifying Session Before Recording Cost (budget-tracker.ts:145-151)

The recordSessionCost method calls db.updateSessionCost() silently. If the session doesn't exist, it fails silently. Consider logging a warning when the update returns false.

📊 Test Coverage

Excellent: 462 budget tracker tests + 426 config tests = 888 tests for new features.

Minor gap: No explicit tests for the validation bypass issue identified above (setting negative values via CLI).

🎯 Recommendations Summary

Medium Priority (fix before merge):

  1. Route budget keys through setBudgetConfig() to enforce validation

Low Priority (post-merge follow-ups):
2. Generalize kebab-to-camelCase normalization
3. Add Number.isFinite() check for non-budget numeric parsing
4. Consider logging warning for invalid session IDs in recordSessionCost

Overall

Recommendation: ✅ Request Changes - Fix validation bypass before merging

The code is production-quality with comprehensive features and excellent test coverage. However, the validation bypass in the config set command is a real concern that allows users to set invalid budget values. Once this is fixed, the PR will be ready for merge.

@github-actions
Copy link

Posted comprehensive PR review identifying one medium-priority validation bypass issue where budget keys skip setBudgetConfig() validation, allowing users to set negative values and invalid threshold ranges.

New%20session%20-%202026-01-30T05%3A41%3A50.813Z
opencode session  |  github run

Test User added 2 commits January 29, 2026 22:50
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.
@github-actions
Copy link

Code Review: Budget Tracking and Enforcement (v1.1)

Overall Assessment

This 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 Addressed

All critical and medium-priority issues from prior reviews have been resolved:

  • ✅ Database connection leak fixed (commit 4c9ced3)
  • ✅ Float precision in Set keys fixed with integer percentages (commit 58c778d)
  • ✅ E2B pricing made configurable via e2bHourlyRate (commit 58c778d)
  • ✅ Debounced config writes implemented (commit 58c778d)
  • ✅ UTC-based periods documented in JSDoc (commit 58c778d)
  • ✅ Period rollover warning reset implemented (commit 58c778d)
  • ✅ Config key validation prevents prototype pollution (commit 4c9ced3)
  • ✅ Budget validation bypass fixed via setBudgetConfig() routing (commit f18f53f)
  • e2bHourlyRate validation added (commit 8acc228)
  • ✅ NaN validation in threshold parsing (commit 8acc228)
  • ✅ Division by zero protection added (commit 8acc228)
  • ✅ Shared array reference fixed with structuredClone (commit 8acc228)
  • ✅ Config file backup on corruption (commit 55cd918)

🔍 New Observations

1. Limited Kebab-to-CamelCase Normalization (Low Priority)

Location: src/cli.ts:3041-3043

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 Infinity (Low Priority)

Location: src/cli.ts:3066-3067

For non-budget numeric values:

} else if (!isNaN(parseFloat(value))) {
  parsedValue = parseFloat(value);

This accepts parseFloat('Infinity') as valid. While unlikely to cause issues, consider using Number.isFinite() for stricter validation.


3. Silent Session Cost Update Failure (Low Priority)

Location: src/budget-tracker.ts:174

The recordSessionCost method doesn't check if db.updateSessionCost() succeeded:

this.db.updateSessionCost(sessionId, cost);

If the session doesn't exist, it fails silently. Consider logging a warning for debugging purposes.


4. CLI Command Naming (Low Priority - Convention)

Location: src/cli.ts:3182

The command budget-status uses hyphenated naming, which conflicts with the project's coding guideline:

Prefer proper subcommands over hyphenated command names (e.g., parallel-cc templates list instead of parallel-cc templates-list)

Consider using parallel-cc budget status instead, keeping budget-status as an alias for backward compatibility.

✅ Well-Implemented Features

  1. Budget Validation Routing: CLI correctly routes budget.* keys through setBudgetConfig() ensuring all validation applies
  2. Period Rollover Detection: Automatic warning reset when period boundaries change
  3. Config Safety: Prototype pollution prevention and corrupt file backup
  4. Type Safety: Integer percentage Set keys avoid float precision issues
  5. Database Management: Proper connection cleanup with finally blocks

📊 Test Coverage

Excellent: 462 budget tracker tests + 426 config tests + 382 sandbox enforcement tests = 1,270 tests

Minor gaps:

  • No tests for concurrent BudgetTracker instances (unlikely in practice)
  • No explicit tests for the key normalization edge cases noted above

🎯 Recommendations Summary

High Priority: None - all critical issues resolved

Medium Priority: None

Low Priority (post-merge or follow-up):

  1. Generalize kebab-to-camelCase normalization for future key additions
  2. Consider Number.isFinite() validation for non-budget numeric parsing
  3. Log warning when session cost update fails
  4. Consider CLI command naming convention (budget status vs budget-status)

Overall

Recommendation:Approve

This is production-quality code. The feature is comprehensively tested, well-architected, and all previously identified issues have been addressed. The implementation follows project conventions, includes proper security hardening, and demonstrates thoughtful error handling throughout.

The budget tracking system is a valuable addition for users managing E2B sandbox costs.

@github-actions
Copy link

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:

  • ✅ All validation bypass issues fixed
  • ✅ Database cleanup properly handled
  • ✅ Float precision, pricing, and security concerns resolved
  • ✅ Proper error handling and type safety throughout

New low-priority observations:

  1. Limited kebab-to-camelCase normalization (only handles 3 keys)
  2. Non-budget numeric parsing accepts Infinity values
  3. Silent failures in session cost updates
  4. CLI command naming slightly偏离s project conventions

Recommendation:Approve

The feature is well-tested, secure, and ready for merge. The low-priority items can be addressed in future iterations.

New%20session%20-%202026-01-30T05%3A51%3A11.994Z
opencode session  |  github run

if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
throw err;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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().

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 a budget status subcommand instead of budget-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 list instead of parallel-cc templates-list).

Comment on lines +3040 to +3043
// 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');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +315 to +343
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');
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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
@github-actions
Copy link

Code Review: Budget Tracking and Enforcement (v1.1)

Overall Assessment

This 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 Resolved

The following issues identified in earlier reviews have been fixed:

  • ✅ Database connection leak (commit 4c9ced3)
  • ✅ Float precision in Set keys (integer percentages used)
  • ✅ Period rollover warning reset implemented
  • ✅ Config key validation prevents prototype pollution (commit 4c9ced3)
  • ✅ Budget validation routing through setBudgetConfig() (commit f18f53f)
  • e2bHourlyRate validation added (commit 8acc228)
  • ✅ NaN validation in threshold parsing (commit 8acc228)
  • ✅ Division by zero protection (commit 8acc228)
  • ✅ Shared array reference fixed with structuredClone (commit 8acc228)
  • ✅ Config file backup on corruption (commit 55cd918)

🔍 Remaining Low-Priority Observations

1. Missing e2b-hourly-rate Key Mapping (cli.ts:3041-3046)

The key normalization chain doesn't map e2b-hourly-rate to e2bHourlyRate:

const normalizedKey = key.replace('monthly-limit', 'monthlyLimit')
  .replace('per-session-default', 'perSessionDefault')
  .replace('warning-thresholds', 'warningThresholds');

Impact: config set budget.e2b-hourly-rate 0.12 won't update the e2bHourlyRate field.

Suggestion: Add .replace('e2b-hourly-rate', 'e2bHourlyRate') to the chain in all three occurrences.


2. NaN/Infinity Validation Not Comprehensive (config.ts:319-343)

The setBudgetConfig() validation only checks < 0, allowing NaN and Infinity:

if (config.monthlyLimit !== undefined && config.monthlyLimit !== null) {
  if (config.monthlyLimit < 0) { // NaN and Infinity pass this check
    throw new Error('Budget limit must be a positive number');
  }
}

Suggestion: Use Number.isFinite() for robust validation:

if (!Number.isFinite(config.monthlyLimit) || config.monthlyLimit < 0) {

3. Non-Budget Numeric Parsing Accepts Infinity (cli.ts:3066-3067)

} else if (!isNaN(parseFloat(value))) {
  parsedValue = parseFloat(value);

Impact: parseFloat('Infinity') is accepted for non-budget numeric values.

Suggestion: Consider using Number.isFinite() for stricter validation.


4. Limited Kebab-to-CamelCase Normalization (cli.ts:3041-3043)

The normalization only handles specific keys. If more budget keys are added, this needs manual updates.

Suggestion: Use a general regex for maintainability:

const normalizedKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());

5. CLI Command Naming (cli.ts:3182)

The command budget-status uses hyphenated naming, slightly偏离 the project guideline:

Prefer proper subcommands over hyphenated command names

Observation: Could use parallel-cc budget status with budget-status as an alias.

✅ Well-Implemented Features

  1. Budget Validation Routing: CLI correctly routes budget.* keys through setBudgetConfig()
  2. Period Rollover Detection: Automatic warning reset on period boundaries
  3. Config Safety: Prototype pollution prevention and corrupt file backup
  4. Type Safety: Integer percentage Set keys avoid float precision
  5. Database Management: Proper connection cleanup with finally blocks
  6. Debounced Config Writes: 100ms debounce prevents excessive I/O

📊 Test Coverage

Excellent: 1,270 tests (462 budget tracker + 426 config + 382 sandbox enforcement)

🎯 Recommendations Summary

High Priority: None - all critical issues resolved

Medium Priority: None

Low Priority (post-merge or follow-up):

  1. Add e2b-hourly-rate key mapping in normalization chain
  2. Use Number.isFinite() for comprehensive budget validation
  3. Consider Number.isFinite() for non-budget numeric parsing
  4. Generalize kebab-to-camelCase normalization for future key additions

Overall

Recommendation: ✅ Approve

This is production-quality code with comprehensive features and excellent test coverage. The architecture is sound, all previously identified critical/medium issues have been addressed, and the implementation follows project conventions. The remaining low-priority items are minor 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.

@github-actions
Copy link

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.

New%20session%20-%202026-01-30T06%3A02%3A24.194Z
opencode session  |  github run

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

@frankbria frankbria merged commit 3bd55ae into main Jan 30, 2026
5 checks passed
@frankbria frankbria deleted the claude/add-budget-limits-eMu6m branch January 30, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants