Skip to content

Conversation

@medevs
Copy link

@medevs medevs commented Jan 3, 2026

Summary

This PR adds graceful handling when Claude Code usage limits are reached during auto-mode execution.

Changes

  • User-friendly dialog: Shows a popup when usage limits are reached instead of generic errors
  • Scheduled resume: Users can schedule auto-mode to resume after a specified time (15m, 30m, 1h, 2h, or custom)
  • Proactive usage check: Checks usage limits before starting auto-mode to prevent failed task attempts
  • Persistent schedules: Scheduled resumes persist across app restarts
  • Visual indicator: Shows "Paused - At Limit" indicator with countdown timer in the board header
  • Better error detection: Detects ambiguous CLI exits as potential quota issues

Files Changed

Server (4 files)

  • apps/server/src/services/auto-mode-service.ts - Added usage limit detection and suggested resume time
  • apps/server/tests/unit/services/auto-mode-service.test.ts - Added ClaudeUsageService mocks
  • apps/server/tests/unit/services/auto-mode-service-usage-limit.test.ts - New unit tests
  • apps/server/tests/integration/services/auto-mode-service.integration.test.ts - Added mocks

UI (9 files)

  • apps/ui/src/store/app-store.ts - Added autoModePausedByProject state
  • apps/ui/src/components/views/board-view.tsx - Added proactive usage checks
  • apps/ui/src/components/views/board-view/board-header.tsx - Added pause indicator
  • apps/ui/src/hooks/use-auto-mode.ts - Handle pause events
  • apps/ui/src/hooks/use-auto-mode-scheduler.ts - New scheduler hook
  • apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx - New dialog component
  • apps/ui/src/components/dialogs/index.ts - Export new dialog
  • apps/ui/src/types/electron.d.ts - Updated event types
  • apps/ui/src/routes/__root.tsx - Render dialog

E2E Tests (1 file)

  • apps/ui/tests/features/auto-mode-usage-limit.spec.ts - Comprehensive E2E tests

Utils (2 files)

  • libs/utils/src/error-handler.ts - Added isAmbiguousCLIExit function
  • libs/utils/src/index.ts - Export new function

Documentation (1 file)

  • docs/USAGE_LIMIT_FEATURE_REPORT.md - Feature documentation

Testing

  • All 93 auto-mode tests pass ✅
  • Build passes ✅
  • Files formatted with Prettier ✅

Summary by CodeRabbit

Release Notes

  • New Features
    • Auto Mode now detects Claude API usage limits and automatically pauses to prevent quota errors
    • Resume Dialog allows scheduling when Auto Mode should restart with quick presets or custom timing
    • UI indicator displays Auto Mode paused status and shows countdown to scheduled resume time
    • Suggested resume times automatically calculated based on API rate limit resets

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

- Show user-friendly dialog when usage limits are reached
- Allow users to schedule auto mode resume at a later time
- Detect ambiguous CLI exits as potential quota issues
- Add proactive usage check before starting auto mode
- Persist scheduled resumes across app restarts
- Add visual indicator showing paused state and countdown
- Include comprehensive tests for the feature
@coderabbitai
Copy link

coderabbitai bot commented Jan 3, 2026

📝 Walkthrough

Walkthrough

This PR implements a comprehensive usage-limit feature for Auto Mode, adding proactive Claude API usage checks, pause/resume scheduling, and event enrichment across backend and frontend systems. It integrates ClaudeUsageService to detect session and weekly quota exhaustion, pause Auto Mode with suggested resume times, and persist pause/resume state in the UI with a new dialog for scheduling resumption.

Changes

Cohort / File(s) Summary
Backend Service Integration
apps/server/src/services/auto-mode-service.ts
Added ClaudeUsageService import and integration. Made signalShouldPause asynchronous to fetch usage data. Introduced startup usage-check logic in startAutoLoop to detect session/weekly limits before loop begins. Enhanced trackFailureAndCheckPause to handle isAmbiguousCLIExit errors. Event payloads now include suggestedResumeAt and lastKnownUsage for UI-driven resume guidance.
Backend Service Tests
apps/server/tests/integration/services/auto-mode-service.integration.test.ts, apps/server/tests/unit/services/auto-mode-service-usage-limit.test.ts, apps/server/tests/unit/services/auto-mode-service.test.ts
Added mocking of ClaudeUsageService with mock usage data. Integrated usage checks into test setup to simulate normal and at-limit scenarios. New unit tests cover startup usage-check flows, pause event emission with resume times, limit prioritization, error degradation, and end-to-end workflows. Existing tests updated to avoid interference from usage checks.
Error Detection & Utilities
libs/utils/src/error-handler.ts, libs/utils/src/index.ts
Added isAmbiguousCLIExit function to detect generic "process exited with code" errors that may mask quota issues. Updated classifyError to treat ambiguous CLI exits as quota-exhausted scenarios and export the flag in public ErrorInfo. Re-exported new utility function.
Frontend UI Components
apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx, apps/ui/src/components/dialogs/index.ts
New AutoModeResumeDialog component for scheduling auto-mode resumption. Fetches usage data on open, computes earliest reset time, offers duration presets (15m, 30m, 1h, 1h30m, 2h) and custom minutes. Displays resume-time preview and persists schedule via store.
Frontend Header & Board View
apps/ui/src/components/views/board-view.tsx, apps/ui/src/components/views/board-view/board-header.tsx
Board view now detects usage limits on auto-mode start and pauses with reason. Integrated useAutoModeScheduler hook for managing scheduled resumes. BoardHeader extended with pause/resume indicator showing scheduled resume countdown or "at-limit" status with tooltip. New props: isAutoModePaused, autoModePausedState, autoModeResumeSchedule, onOpenPauseDialog.
Frontend State Management
apps/ui/src/store/app-store.ts
Introduced AutoModeResumeSchedule and AutoModePausedState types. Added per-project storage for resume schedules and paused states, plus dialog state (autoModePauseDialogOpen, autoModePauseReason). Implemented 9 new AppActions for managing schedules, paused states, and dialog visibility. Integrated with persistence layer.
Frontend Hooks & Event Handling
apps/ui/src/hooks/use-auto-mode.ts, apps/ui/src/hooks/use-auto-mode-scheduler.ts
useAutoMode now handles auto_mode_paused_failures events and defers quota/rate-limit errors to pause-dialog pathway. useAutoModeScheduler is a new hook managing per-project timeout scheduling, resume logic with usage re-checks, and user notifications via dialogs/toasts.
Type Definitions
apps/ui/src/types/electron.d.ts
Extended AutoModeEvent union with new auto_mode_paused_failures variant including pause reason, suggested resume time, and usage data. Added rate_limit and quota_exhausted to auto_mode_error errorType literals.
Root Layout & E2E Tests
apps/ui/src/routes/__root.tsx, apps/ui/tests/features/auto-mode-usage-limit.spec.ts
Root layout now unconditionally renders AutoModeResumeDialog. New comprehensive E2E test suite validates pause/resume dialog display, scheduling, cancellation, and indicator rendering across usage-limit scenarios.
Documentation
docs/USAGE_LIMIT_FEATURE_REPORT.md
Feature report documenting architecture, data structures, user flows, testing plan, edge cases, and troubleshooting for the usage-limit feature.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as BoardView
    participant Hook as useAutoModeScheduler
    participant Store as AppStore
    participant Electron as Electron API
    participant Backend as AutoModeService

    User->>UI: Enable Auto Mode
    UI->>Store: Check isClaudeUsageAtLimit
    alt Usage at limit
        UI->>Store: setAutoModePaused(reason: usage_limit)
        UI->>Store: openAutoModePauseDialog
        UI->>User: Show pause dialog with reset times
    else Usage OK
        UI->>Backend: startAutoLoop
        Backend->>Backend: Check usage proactively
        alt Limit reached at startup
            Backend->>Store: Emit auto_mode_paused_failures event
            Backend->>UI: Signal pause with suggestedResumeAt
        else Limit OK
            Backend->>Backend: Start loop normally
        end
    end
Loading
sequenceDiagram
    actor User
    participant UI as AutoModeResumeDialog
    participant Hook as useAutoModeScheduler
    participant Store as AppStore
    participant Electron as Electron API
    participant Backend as AutoModeService

    User->>UI: Open pause dialog
    UI->>Electron: Fetch Claude usage data
    Electron-->>UI: Return usage with reset times
    UI->>UI: Compute earliest reset time
    User->>UI: Select duration preset or custom
    User->>UI: Schedule Resume
    UI->>Store: setAutoModeResumeSchedule(resumeAt)
    Hook->>Hook: scheduleResume(resumeAt, projectId)
    Hook->>Hook: Set timeout for resumeAt
    rect rgb(200, 220, 200)
        Note over Hook: Time elapses...
    end
    Hook->>Hook: resumeAutoMode triggered
    Hook->>Electron: Re-check usage
    alt Still at limit
        Hook->>Store: setAutoModePaused (keep paused)
        Hook->>User: Show notification (still at limit)
    else Below limit
        Hook->>Backend: startAutoLoop
        Hook->>Store: Clear paused state
        Hook->>User: Show success toast
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐇 Hoppy pauses, usage checked,
Quota limits now detected!
Reschedule resumes with a tap,
No more Claude API mishap!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and directly summarizes the main change: adding graceful handling for Claude Code usage limits, which is the primary objective of the PR.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @medevs, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a comprehensive system for gracefully handling Claude Code usage limits within the application's auto-mode. The primary goal is to enhance user experience by replacing cryptic errors with clear, actionable feedback and providing tools to manage auto-mode when limits are encountered. This includes proactive checks, an interactive dialog for scheduling resume times, persistent schedules, and visual indicators, ensuring a smoother workflow even under usage constraints.

Highlights

  • Graceful Usage Limit Handling: Implements a user-friendly dialog and scheduling mechanism when Claude Code usage limits are reached, preventing generic errors and improving the user experience.
  • Proactive Usage Checks: Auto-mode now checks for usage limits before starting, avoiding failed task attempts and immediately informing the user if limits are exceeded.
  • Scheduled Auto-Mode Resume: Users can schedule auto-mode to resume after a specified time (e.g., 15m, 30m, 1h, or custom), with schedules persisting across app restarts.
  • Visual Pause Indicator: A "Paused - At Limit" indicator with a countdown timer is displayed in the board header, providing clear visual feedback on auto-mode status.
  • Enhanced Error Detection: Improved error classification now detects ambiguous CLI exits as potential quota issues, triggering the graceful pause mechanism.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust feature for gracefully handling Claude API usage limits. The changes are comprehensive, spanning the backend, UI, state management, and testing. Key additions include proactive usage checks before starting auto-mode, a user-friendly dialog for scheduling resumes when limits are hit, and persistent scheduling that survives app restarts. The code is well-structured, and the new E2E and unit tests provide good coverage for the new functionality. I've identified a few areas with code duplication that could be refactored to improve maintainability, but overall this is a solid implementation of a valuable feature.

Comment on lines +276 to +313
// Calculate suggested resume time based on reset times
const sessionReset = (usage as { sessionResetTime?: string }).sessionResetTime;
const sessionResetText = (usage as { sessionResetText?: string }).sessionResetText;
const weeklyReset = (usage as { weeklyResetTime?: string }).weeklyResetTime;
const weeklyResetText = (usage as { weeklyResetText?: string }).weeklyResetText;

if (sessionReset) {
// Check if it's already an ISO date string
const resetDate = new Date(sessionReset);
if (!isNaN(resetDate.getTime())) {
suggestedResumeAt = sessionReset;
} else if (sessionResetText) {
// Parse "Resets in Xh Ym" format from text
const match = sessionResetText.match(/(\d+)h\s*(\d+)?m?/);
if (match) {
const hours = parseInt(match[1], 10);
const minutes = parseInt(match[2] || '0', 10);
const resetMs = (hours * 60 + minutes) * 60 * 1000;
suggestedResumeAt = new Date(Date.now() + resetMs).toISOString();
}
}
} else if (weeklyReset) {
// Check if it's already an ISO date string
const resetDate = new Date(weeklyReset);
if (!isNaN(resetDate.getTime())) {
suggestedResumeAt = weeklyReset;
} else if (weeklyResetText) {
// Parse text format
const match = weeklyResetText.match(/(\d+)h\s*(\d+)?m?/);
if (match) {
const hours = parseInt(match[1], 10);
const minutes = parseInt(match[2] || '0', 10);
const resetMs = (hours * 60 + minutes) * 60 * 1000;
suggestedResumeAt = new Date(Date.now() + resetMs).toISOString();
}
}
}
}
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 logic for calculating suggestedResumeAt is duplicated in signalShouldPause and startAutoLoop. This increases maintenance overhead and risk of inconsistencies. Consider extracting this into a private helper method. This would also be a good opportunity to make the logic in signalShouldPause more robust by checking usage.sessionPercentage and usage.weeklyPercentage before deciding which reset time to use, similar to the logic in startAutoLoop.

For example, you could create a helper function:

private parseResumeTime(resetTime?: string, resetText?: string): string | undefined {
  // ... parsing logic ...
}

And then use it in both places.

Comment on lines +50 to +59
const formatTimeRemaining = (resumeAt: string) => {
const now = new Date();
const resumeDate = new Date(resumeAt);
const diffMs = resumeDate.getTime() - now.getTime();
if (diffMs <= 0) return 'resuming soon...';
const hours = Math.floor(diffMs / (1000 * 60 * 60));
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This formatTimeRemaining function is identical to formatTimeUntil in auto-mode-resume-dialog.tsx. To avoid code duplication, consider moving this utility function to a shared location, for example in libs/utils/src or a new apps/ui/src/lib/time-utils.ts file, and import it in both components.

if (api?.claude?.getUsage) {
const usage = await api.claude.getUsage();
if (!('error' in usage)) {
const isAtLimit = usage.sessionPercentage >= 100 || usage.weeklyPercentage >= 100;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic to check if usage is at the limit is already implemented in the isClaudeUsageAtLimit utility function in app-store.ts. To ensure consistency and avoid duplication, please import and use that function here.

You'll need to add isClaudeUsageAtLimit to your import from @/store/app-store:

import { useAppStore, isClaudeUsageAtLimit } from '@/store/app-store';
Suggested change
const isAtLimit = usage.sessionPercentage >= 100 || usage.weeklyPercentage >= 100;
const isAtLimit = isClaudeUsageAtLimit(usage);

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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/server/src/services/auto-mode-service.ts (2)

757-762: Async signalShouldPause is not awaited, risking unhandled promise rejection.

signalShouldPause was changed to be async (line 255), but this call site doesn't await it. If the usage data fetch or any operation inside fails, the rejection won't be caught, potentially causing an unhandled promise rejection.

🔎 Proposed fix
         if (shouldPause) {
-          this.signalShouldPause({
+          // Fire and forget with error handling - we don't want to block feature cleanup
+          this.signalShouldPause({
             type: errorInfo.type,
             message: errorInfo.message,
-          });
+          }).catch(err => {
+            console.error('[AutoMode] Failed to signal pause:', err);
+          });
         }

1190-1195: Same issue: async signalShouldPause not awaited in followUpFeature.

🔎 Proposed fix
         if (shouldPause) {
           this.signalShouldPause({
             type: errorInfo.type,
             message: errorInfo.message,
-          });
+          }).catch(err => {
+            console.error('[AutoMode] Failed to signal pause:', err);
+          });
         }
♻️ Duplicate comments (1)
apps/server/tests/unit/services/auto-mode-service.test.ts (1)

13-28: Same duplication note as integration tests.

As noted in the integration test review, this mock data is duplicated and could benefit from extraction to a shared test helper.

🧹 Nitpick comments (8)
apps/server/tests/integration/services/auto-mode-service.integration.test.ts (1)

33-48: Consider extracting shared mock data to a test helper.

The mockUsageOK structure is duplicated in both the integration test file (here) and the unit test file (apps/server/tests/unit/services/auto-mode-service.test.ts lines 14-28). Consider extracting this to a shared test helper file to maintain consistency and reduce duplication.

💡 Suggested refactor

Create a shared test helper file:

// apps/server/tests/helpers/usage-mock-data.ts
export const mockUsageOK = {
  sessionPercentage: 50,
  weeklyPercentage: 60,
  sessionResetTime: null,
  weeklyResetTime: null,
  sonnetWeeklyPercentage: 0,
  sessionTokensUsed: 0,
  sessionLimit: 0,
  costUsed: null,
  costLimit: null,
  costCurrency: null,
  sessionResetText: '',
  weeklyResetText: '',
  userTimezone: 'UTC',
};

Then import in both test files:

+import { mockUsageOK } from '../helpers/usage-mock-data.js';
-  // Mock usage data that indicates normal operation (not at limit)
-  const mockUsageOK = {
-    sessionPercentage: 50,
-    ...
-  };
apps/server/tests/unit/services/auto-mode-service.test.ts (1)

50-51: Consider more robust synchronization instead of fixed delay.

The 50ms delay may be insufficient in slower CI environments or under load, potentially causing test flakiness. Consider using a polling mechanism or event-based synchronization to ensure the usage check completes.

💡 Suggested alternative approach

Instead of a fixed delay, poll for the condition:

-      // Wait for the first loop to actually start (usage check + set running flag)
-      await new Promise((resolve) => setTimeout(resolve, 50));
+      // Wait for the usage check to complete and running flag to be set
+      await vi.waitFor(() => {
+        // Try to start second loop - should be rejected once first loop is running
+        return service.startAutoLoop('/test/project', 3).catch(() => true);
+      }, { timeout: 1000 });

Or verify the mock was called:

+      // Wait for usage check to complete
+      await vi.waitFor(() => {
+        expect(ClaudeUsageService.prototype.fetchUsageData).toHaveBeenCalled();
+      }, { timeout: 1000 });
apps/ui/src/hooks/use-auto-mode.ts (1)

362-370: Missing dependencies in useEffect for event handler.

The event handler uses setAutoModePaused and openAutoModePauseDialog but these are not included in the dependency array. While Zustand store actions are typically stable references, adding them ensures correctness and silences potential linter warnings.

🔎 Proposed fix
   return unsubscribe;
  }, [
    projectId,
    addRunningTask,
    removeRunningTask,
    addAutoModeActivity,
    getProjectIdFromPath,
    setPendingPlanApproval,
    currentProject?.path,
+   setAutoModePaused,
+   openAutoModePauseDialog,
  ]);
apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx (1)

68-68: Consider showing loading state while fetching usage.

The fetchingUsage state is tracked but not reflected in the UI. Users may see stale data briefly or no reset time information while the fetch is in progress. Consider adding a loading indicator or disabling the "Resume at reset" button until the fetch completes.

🔎 Example loading state usage
                {/* Resume at reset button */}
-               {resetTime && (
+               {resetTime && !fetchingUsage && (
                  <Button
                    variant={useResetTime ? 'default' : 'outline'}
                    size="sm"
                    onClick={handleUseResetTime}

Or add a skeleton/spinner while loading:

{fetchingUsage && (
  <div className="text-sm text-muted-foreground">Loading usage data...</div>
)}

Also applies to: 106-127

apps/server/tests/unit/services/auto-mode-service-usage-limit.test.ts (1)

58-68: Potential test flakiness with arbitrary timeouts.

Using setTimeout(resolve, 50) to wait for async operations can lead to flaky tests, especially in slower CI environments. Consider using more deterministic approaches like:

  • Awaiting specific state changes
  • Using vi.waitFor or similar utilities
  • Emitting test-specific events
🔎 Example with waitFor pattern
// Instead of:
await new Promise((resolve) => setTimeout(resolve, 50));

// Consider:
await vi.waitFor(() => {
  expect(ClaudeUsageService.prototype.fetchUsageData).toHaveBeenCalled();
});
apps/ui/src/components/views/board-view/board-header.tsx (1)

49-59: Time remaining display may become stale without periodic updates.

The formatTimeRemaining function calculates time based on new Date() at render time, but there's no mechanism to trigger re-renders as time passes. The displayed countdown will only update when other state changes cause re-renders.

Consider adding a timer effect to periodically update the display:

🔎 Proposed fix using a timer
+import { useEffect, useState } from 'react';
+
+// Inside BoardHeader component, before formatTimeRemaining:
+const [, forceUpdate] = useState(0);
+
+// Add effect to update countdown every minute when paused with schedule
+useEffect(() => {
+  if (!isAutoModePaused || !autoModeResumeSchedule?.resumeAt) return;
+  
+  const interval = setInterval(() => {
+    forceUpdate(n => n + 1);
+  }, 60000); // Update every minute
+  
+  return () => clearInterval(interval);
+}, [isAutoModePaused, autoModeResumeSchedule?.resumeAt]);
apps/ui/src/hooks/use-auto-mode-scheduler.ts (1)

177-207: Effect may cause memory leaks if projects change rapidly.

The cleanup function clears all timeouts, but if autoModeResumeByProject or projects change frequently, the effect will re-run and potentially lose track of timeouts that were set in previous runs before they're properly cleared.

The current implementation looks generally correct, but consider adding a guard to prevent scheduling if a timeout already exists for the project:

🔎 Optional: Add duplicate scheduling guard
     // Process all scheduled resumes
     Object.entries(autoModeResumeByProject).forEach(([projectId, schedule]) => {
       if (!schedule?.resumeAt) return;
+      
+      // Skip if already scheduled (prevents duplicate timeouts)
+      if (timeoutsRef.current.has(projectId)) return;

       const resumeTime = new Date(schedule.resumeAt);
apps/ui/src/store/app-store.ts (1)

2783-2786: Consider removing key instead of setting to null for consistency.

setAutoModePaused sets the value to null when clearing, while setAutoModeResumeSchedule removes the key entirely when null is passed. This inconsistency could lead to accumulating null entries in autoModePausedByProject over time.

🔎 Suggested fix for consistency
       // Auto Mode Paused State actions
       setAutoModePaused: (projectId: string, state: AutoModePausedState | null) => {
         const current = get().autoModePausedByProject;
-        set({ autoModePausedByProject: { ...current, [projectId]: state } });
+        if (state === null) {
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          const { [projectId]: _, ...rest } = current;
+          set({ autoModePausedByProject: rest });
+        } else {
+          set({ autoModePausedByProject: { ...current, [projectId]: state } });
+        }
       },
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f34fd95 and 1eac65b.

📒 Files selected for processing (17)
  • apps/server/src/services/auto-mode-service.ts
  • apps/server/tests/integration/services/auto-mode-service.integration.test.ts
  • apps/server/tests/unit/services/auto-mode-service-usage-limit.test.ts
  • apps/server/tests/unit/services/auto-mode-service.test.ts
  • apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx
  • apps/ui/src/components/dialogs/index.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/board-header.tsx
  • apps/ui/src/hooks/use-auto-mode-scheduler.ts
  • apps/ui/src/hooks/use-auto-mode.ts
  • apps/ui/src/routes/__root.tsx
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/types/electron.d.ts
  • apps/ui/tests/features/auto-mode-usage-limit.spec.ts
  • docs/USAGE_LIMIT_FEATURE_REPORT.md
  • libs/utils/src/error-handler.ts
  • libs/utils/src/index.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/utils/src/index.ts
  • apps/ui/src/components/dialogs/index.ts
  • apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx
  • apps/ui/src/hooks/use-auto-mode-scheduler.ts
  • apps/ui/src/types/electron.d.ts
  • apps/server/tests/unit/services/auto-mode-service-usage-limit.test.ts
  • apps/server/tests/unit/services/auto-mode-service.test.ts
  • apps/ui/src/routes/__root.tsx
  • libs/utils/src/error-handler.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/board-header.tsx
  • apps/ui/tests/features/auto-mode-usage-limit.spec.ts
  • apps/server/tests/integration/services/auto-mode-service.integration.test.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/hooks/use-auto-mode.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/utils/src/index.ts
  • apps/ui/src/components/dialogs/index.ts
  • apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx
  • apps/ui/src/hooks/use-auto-mode-scheduler.ts
  • apps/ui/src/types/electron.d.ts
  • apps/server/tests/unit/services/auto-mode-service-usage-limit.test.ts
  • apps/server/tests/unit/services/auto-mode-service.test.ts
  • apps/ui/src/routes/__root.tsx
  • libs/utils/src/error-handler.ts
  • apps/server/src/services/auto-mode-service.ts
  • apps/ui/src/components/views/board-view/board-header.tsx
  • apps/ui/tests/features/auto-mode-usage-limit.spec.ts
  • apps/server/tests/integration/services/auto-mode-service.integration.test.ts
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/hooks/use-auto-mode.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/services/auto-mode-service.ts
🧠 Learnings (3)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx
  • apps/ui/src/routes/__root.tsx
  • apps/ui/src/components/views/board-view/board-header.tsx
  • apps/ui/src/components/views/board-view.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/server/src/services/auto-mode-service.ts
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Store project-specific rules in `.automaker/context/` and load them into agent prompts via `loadContextFiles()` from automaker/utils

Applied to files:

  • apps/server/src/services/auto-mode-service.ts
🧬 Code graph analysis (10)
apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx (3)
apps/ui/src/store/app-store.ts (1)
  • AutoModeResumeSchedule (606-611)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (728-737)
apps/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/ui/src/hooks/use-auto-mode-scheduler.ts (1)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (728-737)
apps/server/tests/unit/services/auto-mode-service.test.ts (1)
apps/server/src/services/claude-usage-service.ts (1)
  • ClaudeUsageService (17-496)
apps/ui/src/routes/__root.tsx (2)
apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx (1)
  • AutoModeResumeDialog (53-312)
apps/ui/src/components/dialogs/index.ts (1)
  • AutoModeResumeDialog (1-1)
libs/utils/src/error-handler.ts (2)
libs/utils/src/index.ts (2)
  • isAmbiguousCLIExit (13-13)
  • extractRetryAfter (14-14)
libs/types/src/error.ts (1)
  • ErrorType (4-11)
apps/server/src/services/auto-mode-service.ts (2)
libs/utils/src/error-handler.ts (1)
  • isAmbiguousCLIExit (128-150)
apps/server/src/services/claude-usage-service.ts (1)
  • ClaudeUsageService (17-496)
apps/ui/tests/features/auto-mode-usage-limit.spec.ts (4)
apps/ui/tests/utils/git/worktree.ts (1)
  • createTempDirPath (52-55)
apps/ui/tests/utils/api/client.ts (1)
  • authenticateForTests (357-361)
apps/ui/tests/utils/core/interactions.ts (1)
  • handleLoginScreenIfPresent (65-115)
apps/ui/tests/utils/core/waiting.ts (1)
  • waitForNetworkIdle (9-11)
apps/server/tests/integration/services/auto-mode-service.integration.test.ts (1)
apps/server/src/services/claude-usage-service.ts (1)
  • ClaudeUsageService (17-496)
apps/ui/src/components/views/board-view.tsx (1)
apps/ui/src/hooks/use-auto-mode-scheduler.ts (1)
  • useAutoModeScheduler (16-229)
apps/ui/src/store/app-store.ts (1)
apps/server/src/routes/claude/types.ts (1)
  • ClaudeUsage (5-28)
🪛 LanguageTool
docs/USAGE_LIMIT_FEATURE_REPORT.md

[style] ~9-~9: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...sume 3. Automatically resumes - Can automatically restart Auto Mode when the scheduled ti...

(ADVERB_REPETITION_PREMIUM)

🔇 Additional comments (32)
apps/ui/src/routes/__root.tsx (2)

25-25: LGTM - Dialog import is correctly integrated.

The import follows the established pattern for dialog components in this file and uses the appropriate path alias.


410-411: LGTM - Dialog rendering is correctly placed.

The dialog is appropriately rendered in the root layout where it can be accessed throughout the application. The component manages its own visibility state via the app store, so unconditional rendering is the correct pattern.

apps/server/tests/integration/services/auto-mode-service.integration.test.ts (2)

5-5: LGTM - Import follows project conventions.


56-58: LGTM - Mock setup correctly prevents blocking usage checks.

The mocking approach properly prevents real Claude CLI calls during integration tests while simulating normal operational conditions.

libs/utils/src/index.ts (1)

13-13: LGTM - Export correctly extends the public API.

The new export is appropriately grouped with related error-handling utilities and follows the established pattern in this barrel file.

apps/ui/src/components/dialogs/index.ts (1)

1-1: LGTM - Dialog export follows established conventions.

The export is correctly positioned alphabetically and follows the barrel export pattern used throughout the codebase.

apps/server/tests/unit/services/auto-mode-service.test.ts (2)

3-3: LGTM - Import follows project conventions.


34-36: LGTM - Mock setup correctly prevents blocking usage checks.

docs/USAGE_LIMIT_FEATURE_REPORT.md (1)

5-9: Well-structured documentation for the Usage Limit Feature.

The documentation comprehensively covers architecture, user flows, data structures, error handling, and testing scenarios. This will be valuable for maintainability and onboarding.

Minor style note: Line 9 has "Automatically resumes - Can automatically restart..." with "automatically" appearing twice. Consider rephrasing to: "Auto-resumes - Can restart Auto Mode when the scheduled time arrives" for better readability.

libs/utils/src/error-handler.ts (1)

189-198: LGTM - Treating ambiguous CLI exits as potential quota issues.

The approach of classifying ambiguous CLI exits (e.g., "process exited with code 1") as quota_exhausted is well-reasoned. This enables proactive pause behavior when the CLI fails without explicit error messages, which often indicates quota limits. The inline comments clearly explain the rationale.

apps/ui/src/types/electron.d.ts (1)

214-234: LGTM - Well-defined event types for usage limit handling.

The new auto_mode_paused_failures event type and extended errorType literals provide a clean contract between the server and UI for pause/resume flows. The optional fields allow graceful degradation when usage data isn't available.

apps/ui/tests/features/auto-mode-usage-limit.spec.ts (1)

62-325: Good test coverage for usage limit scenarios.

The test suite comprehensively covers the key user flows: pause dialog appearance, persistent indicators, scheduling, cancellation, and normal operation. The test isolation with beforeAll/afterAll setup is appropriate.

apps/ui/src/hooks/use-auto-mode.ts (2)

123-149: LGTM - Clean handling of pause events.

The auto_mode_paused_failures handler correctly orchestrates the pause flow: disabling auto mode, persisting the paused state with usage data, and opening the dialog for user interaction. The fallback message and error type defaults are sensible.


164-172: Good UX improvement - avoiding toast spam for quota errors.

Deferring quota/rate limit errors to the pause dialog rather than showing individual error toasts is a user-friendly design choice. The running task is still properly cleaned up.

apps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx (3)

79-104: LGTM - Correct reset time prioritization logic.

The resetTime memo correctly computes the earliest relevant reset time by checking both session and weekly limits. The date validation and comparison logic is sound.


152-164: LGTM - Schedule creation logic is correct.

The schedule payload is properly constructed with all required fields. The early return guards against edge cases where project context is missing.


196-311: Well-designed dialog UI with clear user options.

The dialog provides a good balance of quick actions (preset durations) and flexibility (custom minutes, reset time). Preventing outside clicks ensures users make an explicit choice. The preview of the resume time helps users confirm their selection.

apps/server/tests/unit/services/auto-mode-service-usage-limit.test.ts (2)

306-388: Pragmatic approach to testing internal behavior.

Testing private methods via (service as any) provides valuable coverage for the failure tracking and pause logic. While this couples tests to implementation details, the tradeoff is acceptable here given the complexity of the pause decision logic. Consider extracting these methods to a separate utility if the class grows, which would enable testing them directly.


1-36: Comprehensive test suite with good structure.

The test organization with clear describe blocks for different aspects (startup checks, failure tracking, pause signaling) makes it easy to understand coverage. The setup/teardown pattern with cleanup in afterEach is good practice. Test cases cover the documented feature requirements well.

apps/ui/src/components/views/board-view/board-header.tsx (1)

123-160: LGTM! Well-structured pause/resume indicator with good UX.

The conditional rendering logic correctly handles both scheduled resume and unscheduled pause states. The tooltip provides helpful context, and the visual styling appropriately differentiates between the two states (amber for scheduled, destructive for paused).

Minor note: The buttons use button elements with proper onClick handlers and cursor styling, which is accessible.

apps/ui/src/components/views/board-view.tsx (2)

682-703: LGTM! Proactive usage limit detection with appropriate pause handling.

The early check prevents wasted API calls when limits are already reached. The pause state is properly set with timestamp, reason, and usage data, and the dialog provides clear user feedback.


244-246: LGTM! Hook integration is appropriate.

The useAutoModeScheduler hook is called unconditionally at the component level, following React's rules of hooks.

apps/ui/src/hooks/use-auto-mode-scheduler.ts (1)

1-5: LGTM! Clean hook structure with proper shallow comparison.

The use of useShallow for the selector prevents unnecessary re-renders when unrelated store state changes. The imports follow the shared package pattern per coding guidelines.

Also applies to: 16-37

apps/server/src/services/auto-mode-service.ts (3)

243-247: Good addition: Treating ambiguous CLI exits as potential quota issues.

This defensive approach helps catch quota exhaustion that the SDK doesn't explicitly report. The check is appropriately placed before the return statement.


363-428: LGTM! Proactive usage check at startup prevents wasted attempts.

The early return with appropriate event emission provides good UX by immediately informing users of the limit rather than failing after attempting work. The fallback to text parsing when ISO dates aren't available is robust.


18-18: Verify isAmbiguousCLIExit import path follows shared package convention.

Per coding guidelines, imports should use @automaker/* shared packages. This import from @automaker/utils appears correct.

apps/ui/src/store/app-store.ts (6)

552-570: LGTM!

The new state fields are well-structured for per-project auto-mode pause/resume tracking. The clear separation between resume schedules, paused states, and dialog state follows good state management patterns.


605-619: LGTM!

The new types AutoModeResumeSchedule and AutoModePausedState are well-designed with appropriate fields for tracking pause/resume state. Using ISO timestamp strings for dates ensures proper serialization for persistence.


930-943: LGTM!

The action signatures follow existing store patterns and provide a complete API for managing auto-mode pause/resume state. Having both getAutoModePaused (returns full state) and isAutoModePaused (returns boolean) is a good convenience pattern.


1072-1075: LGTM!

Initial state values are appropriate - empty records for per-project state and dialog closed by default.


2799-2805: LGTM!

Dialog actions correctly manage both the open state and the reason together, ensuring no stale data remains when the dialog is closed.


3066-3069: LGTM!

Persistence configuration is well-designed:

  • autoModeResumeByProject persists to support scheduled resumes surviving app restarts (per PR objectives).
  • autoModePausedByProject persists to maintain "Paused - At Limit" indicator visibility.
  • Dialog state (autoModePauseDialogOpen, autoModePauseReason) is intentionally not persisted, preventing the dialog from auto-opening on app launch.

Comment on lines +267 to +312
let suggestedResumeAt: string | undefined;
let lastKnownUsage: Record<string, unknown> | undefined;

try {
const usageService = new ClaudeUsageService();
if (await usageService.isAvailable()) {
const usage = await usageService.fetchUsageData();
if (usage && !('error' in usage)) {
lastKnownUsage = usage as Record<string, unknown>;
// Calculate suggested resume time based on reset times
const sessionReset = (usage as { sessionResetTime?: string }).sessionResetTime;
const sessionResetText = (usage as { sessionResetText?: string }).sessionResetText;
const weeklyReset = (usage as { weeklyResetTime?: string }).weeklyResetTime;
const weeklyResetText = (usage as { weeklyResetText?: string }).weeklyResetText;

if (sessionReset) {
// Check if it's already an ISO date string
const resetDate = new Date(sessionReset);
if (!isNaN(resetDate.getTime())) {
suggestedResumeAt = sessionReset;
} else if (sessionResetText) {
// Parse "Resets in Xh Ym" format from text
const match = sessionResetText.match(/(\d+)h\s*(\d+)?m?/);
if (match) {
const hours = parseInt(match[1], 10);
const minutes = parseInt(match[2] || '0', 10);
const resetMs = (hours * 60 + minutes) * 60 * 1000;
suggestedResumeAt = new Date(Date.now() + resetMs).toISOString();
}
}
} else if (weeklyReset) {
// Check if it's already an ISO date string
const resetDate = new Date(weeklyReset);
if (!isNaN(resetDate.getTime())) {
suggestedResumeAt = weeklyReset;
} else if (weeklyResetText) {
// Parse text format
const match = weeklyResetText.match(/(\d+)h\s*(\d+)?m?/);
if (match) {
const hours = parseInt(match[1], 10);
const minutes = parseInt(match[2] || '0', 10);
const resetMs = (hours * 60 + minutes) * 60 * 1000;
suggestedResumeAt = new Date(Date.now() + resetMs).toISOString();
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Duplicated reset time parsing logic violates DRY principle.

The same logic for parsing session/weekly reset times from usage data appears twice: once in signalShouldPause (lines 282-312) and once in startAutoLoop (lines 384-408). This duplication makes maintenance harder and increases bug risk.

🔎 Proposed fix: Extract helper method
+  /**
+   * Extract suggested resume time from usage data
+   */
+  private getSuggestedResumeTime(usage: {
+    sessionPercentage?: number;
+    sessionResetTime?: string;
+    sessionResetText?: string;
+    weeklyPercentage?: number;
+    weeklyResetTime?: string;
+    weeklyResetText?: string;
+  }): string | undefined {
+    const isAtSessionLimit = (usage.sessionPercentage ?? 0) >= 100;
+    const isAtWeeklyLimit = (usage.weeklyPercentage ?? 0) >= 100;
+    
+    const parseResetText = (text: string | undefined): string | undefined => {
+      if (!text) return undefined;
+      const match = text.match(/(\d+)h\s*(\d+)?m?/);
+      if (match) {
+        const hours = parseInt(match[1], 10);
+        const minutes = parseInt(match[2] || '0', 10);
+        return new Date(Date.now() + (hours * 60 + minutes) * 60 * 1000).toISOString();
+      }
+      return undefined;
+    };
+    
+    const tryParseResetTime = (resetTime?: string, resetText?: string): string | undefined => {
+      if (resetTime) {
+        const resetDate = new Date(resetTime);
+        if (!isNaN(resetDate.getTime())) return resetTime;
+      }
+      return parseResetText(resetText);
+    };
+    
+    if (isAtSessionLimit) {
+      return tryParseResetTime(usage.sessionResetTime, usage.sessionResetText);
+    } else if (isAtWeeklyLimit) {
+      return tryParseResetTime(usage.weeklyResetTime, usage.weeklyResetText);
+    }
+    return undefined;
+  }

Then use this.getSuggestedResumeTime(usage) in both locations.

Also applies to: 376-408

🤖 Prompt for AI Agents
In apps/server/src/services/auto-mode-service.ts around lines 267-312
(duplication also present around 376-408): the reset-time parsing logic is
duplicated; extract a single private helper on the class like
getSuggestedResumeTime(usage: Record<string, unknown> | any): string | undefined
that encapsulates the current logic (check sessionReset/weeklyReset for ISO,
else parse sessionResetText/weeklyResetText with the hours/minutes regex and
convert to an ISO datetime), return the computed ISO string or undefined, and
use that helper from both signalShouldPause and startAutoLoop; keep existing
lastKnownUsage assignment and type checks, ensure the helper accepts the same
usage shape, is properly typed/nullable, and replace the duplicated blocks with
calls to this.getSuggestedResumeTime(usage).

Comment on lines +853 to +858
// Usage limit checks
claudeUsage,
isAutoModePaused,
setAutoModeRunning,
setAutoModePaused,
openAutoModePauseDialog,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how often claudeUsage is updated in the codebase
rg -n "setClaudeUsage|claudeUsage" --type ts --type tsx -C 2

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Also look at the specific file to understand the effect and dependencies
cat -n apps/ui/src/components/views/board-view.tsx | sed -n '840,880p'

Repository: AutoMaker-Org/automaker

Length of output: 1597


🏁 Script executed:

#!/bin/bash
# Search for useEffect hooks that might use claudeUsage
rg -n "useEffect" apps/ui/src/components/views/board-view.tsx -A 15

Repository: AutoMaker-Org/automaker

Length of output: 5506


🏁 Script executed:

#!/bin/bash
# See the complete useEffect starting at line 677
cat -n apps/ui/src/components/views/board-view.tsx | sed -n '677,750p'

Repository: AutoMaker-Org/automaker

Length of output: 3152


🏁 Script executed:

#!/bin/bash
# Search for claudeUsage in the app-store to understand how it's updated
rg -n "claudeUsage" -C 3 --type ts

Repository: AutoMaker-Org/automaker

Length of output: 19198


🏁 Script executed:

#!/bin/bash
# Check for polling or frequent updates of usage data
rg -n "setClaudeUsage|updateClaudeUsage|poll" -C 2 --type ts

Repository: AutoMaker-Org/automaker

Length of output: 7196


🏁 Script executed:

#!/bin/bash
# Check how pendingFeaturesRef is used within the effect
cat -n apps/ui/src/components/views/board-view.tsx | sed -n '604,850p' | grep -A 5 -B 5 "pendingFeaturesRef"

Repository: AutoMaker-Org/automaker

Length of output: 3065


🏁 Script executed:

#!/bin/bash
# Check if pendingFeaturesRef is reset or cleared in the effect initialization
rg -n "pendingFeaturesRef\s*=" apps/ui/src/components/views/board-view.tsx -A 3

Repository: AutoMaker-Org/automaker

Length of output: 252


Use a ref for claudeUsage to prevent frequent effect re-runs from clearing pendingFeaturesRef.

The usage data is polled every 45 seconds, and including claudeUsage in the dependency array causes this effect to re-run that frequently. Each re-run triggers the cleanup function (line 838) which clears pendingFeaturesRef, breaking the concurrency tracking logic. Follow the existing pattern used for runningAutoTasks and hookFeatures by accessing usage data via a ref instead.

Comment on lines +1077 to +1079
autoModeResumeSchedule={useAppStore
.getState()
.getAutoModeResumeSchedule(currentProject?.id || '')}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Direct store access bypasses React reactivity for resume schedule.

Using useAppStore.getState().getAutoModeResumeSchedule(...) directly in render won't subscribe to store changes. The autoModeResumeSchedule prop passed to BoardHeader won't update when the schedule changes, causing the countdown indicator to remain stale or not appear at all.

🔎 Proposed fix using selector subscription
// Near other useAppStore selectors at the top of the component:
+const autoModeResumeSchedule = useAppStore((state) => 
+  currentProject?.id ? state.autoModeResumeByProject[currentProject.id] : null
+);

// Then in the JSX:
-        autoModeResumeSchedule={useAppStore
-          .getState()
-          .getAutoModeResumeSchedule(currentProject?.id || '')}
+        autoModeResumeSchedule={autoModeResumeSchedule}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/ui/src/components/views/board-view.tsx around lines 1077-1079, the code
uses useAppStore.getState().getAutoModeResumeSchedule(...) directly in render
which bypasses the hook subscription and prevents reactivity; replace that
direct getState call with a proper selector hook: compute a stable projectId
(e.g. currentProject?.id || '') and call useAppStore(state =>
state.getAutoModeResumeSchedule(projectId)) above the JSX, then pass the
resulting autoModeResumeSchedule variable to BoardHeader so the component
re-renders when the store schedule or currentProject id changes.

Comment on lines +56 to +57
const isAtLimit = usage.sessionPercentage >= 100 || usage.weeklyPercentage >= 100;
if (isAtLimit) {
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

Inconsistent usage limit check compared to isClaudeUsageAtLimit.

The limit check here only considers sessionPercentage and weeklyPercentage, but isClaudeUsageAtLimit in the store (per relevant code snippets at lines 624-651) also checks costLimit. This inconsistency could cause the scheduler to resume auto-mode while the board view still considers it at limit.

🔎 Proposed fix - use shared utility
+import { isClaudeUsageAtLimit } from '@/store/app-store';

// Then replace the inline check:
-            const isAtLimit = usage.sessionPercentage >= 100 || usage.weeklyPercentage >= 100;
+            const isAtLimit = isClaudeUsageAtLimit(usage);
📝 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
const isAtLimit = usage.sessionPercentage >= 100 || usage.weeklyPercentage >= 100;
if (isAtLimit) {
import { isClaudeUsageAtLimit } from '@/store/app-store';
// ... rest of code ...
const isAtLimit = isClaudeUsageAtLimit(usage);
if (isAtLimit) {
🤖 Prompt for AI Agents
In apps/ui/src/hooks/use-auto-mode-scheduler.ts around lines 56 to 57, the usage
limit check only tests sessionPercentage and weeklyPercentage but omits
costLimit, causing inconsistent behavior with isClaudeUsageAtLimit in the store;
update this check to use the same shared utility (isClaudeUsageAtLimit) or
mirror its logic by including the costLimit check so auto-mode scheduler and
board view use identical limit logic.

Comment on lines +69 to +76
if (projectPath) {
setAutoModePaused(projectPath, {
pausedAt: new Date().toISOString(),
reason: 'usage_limit',
suggestedResumeAt,
lastKnownUsage: usage,
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: setAutoModePaused called with projectPath instead of projectId.

Based on the store actions from apps/ui/src/store/app-store.ts, setAutoModePaused expects projectId as the first parameter, but this code passes projectPath. This will cause the paused state to be stored under the wrong key, preventing proper state management.

🔎 Proposed fix
               // Keep paused state with updated times
-              if (projectPath) {
-                setAutoModePaused(projectPath, {
+              if (projectId) {
+                setAutoModePaused(projectId, {
                   pausedAt: new Date().toISOString(),
                   reason: 'usage_limit',
                   suggestedResumeAt,
                   lastKnownUsage: usage,
                 });
               }
📝 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
if (projectPath) {
setAutoModePaused(projectPath, {
pausedAt: new Date().toISOString(),
reason: 'usage_limit',
suggestedResumeAt,
lastKnownUsage: usage,
});
}
if (projectId) {
setAutoModePaused(projectId, {
pausedAt: new Date().toISOString(),
reason: 'usage_limit',
suggestedResumeAt,
lastKnownUsage: usage,
});
}
🤖 Prompt for AI Agents
In apps/ui/src/hooks/use-auto-mode-scheduler.ts around lines 69 to 76, the call
to setAutoModePaused is passing projectPath as the first argument but the store
action expects projectId; replace projectPath with the correct projectId value
(e.g., use the existing projectId variable or extract it from the current
project object) so the paused state is stored under the correct key, and keep
the rest of the payload (pausedAt, reason, suggestedResumeAt, lastKnownUsage)
unchanged; also update any local typing or null-checks to ensure projectId is
defined before calling setAutoModePaused.

Comment on lines +101 to +104
// Clear paused state since we're resuming
if (projectPath) {
clearAutoModePaused(projectPath);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: clearAutoModePaused called with projectPath instead of projectId.

Same issue as above - the store action expects projectId, not projectPath. This will fail to clear the paused state.

🔎 Proposed fix
       // Clear paused state since we're resuming
-      if (projectPath) {
-        clearAutoModePaused(projectPath);
+      if (projectId) {
+        clearAutoModePaused(projectId);
       }

Note: You'll need to ensure projectId is passed to the resumeAutoMode function or derive it from the projects array using projectPath.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/ui/src/hooks/use-auto-mode-scheduler.ts around lines 101 to 104,
clearAutoModePaused is being called with projectPath but the store action
expects projectId, so the paused state won't be cleared; update the call to pass
the correct projectId (clearAutoModePaused(projectId)) and ensure resumeAutoMode
has access to that projectId — either change resumeAutoMode's parameters to
accept projectId or derive projectId from the projects array by finding the
project with the given projectPath and using its id before calling
clearAutoModePaused.

Comment on lines +33 to +60
// Helper to mock Claude usage via localStorage/IPC
async function mockClaudeUsage(page: import('@playwright/test').Page, usage: MockUsageData) {
// We'll need to intercept the usage check API calls
await page.evaluate((usageData: MockUsageData) => {
// Store mock data that our app can read
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).__mockClaudeUsage = {
sessionPercentage: usageData.sessionPercentage,
weeklyPercentage: usageData.weeklyPercentage,
sessionResetTime: usageData.sessionResetTime || null,
weeklyResetTime: usageData.weeklyResetTime || null,
sessionTokensUsed: usageData.sessionPercentage * 100,
sessionLimit: 10000,
weeklyTokensUsed: usageData.weeklyPercentage * 100,
weeklyLimit: 10000,
sonnetWeeklyTokensUsed: 0,
sonnetWeeklyPercentage: 0,
sonnetResetText: '',
costUsed: null,
costLimit: null,
costCurrency: null,
sessionResetText: usageData.sessionResetTime ? 'Resets soon' : '',
weeklyResetText: usageData.weeklyResetTime ? 'Resets later' : '',
userTimezone: 'UTC',
lastUpdated: new Date().toISOString(),
};
}, usage);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for __mockClaudeUsage usage in the UI codebase to verify integration
rg -n '__mockClaudeUsage' --type=ts --type=tsx -g '!*.spec.ts' -g '!*.test.ts'

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Search for how usage data is fetched in the app
rg -n 'mockClaudeUsage|fetchUsage|getUsage|usage.*data' --type=ts --type=tsx -g '!*.spec.ts' -g '!*.test.ts' -A 3 -B 1 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Look for window. references in the app that might check for test mocks
rg -n 'window\.__' --type=ts --type=tsx -g '!*.spec.ts' -g '!*.test.ts'

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Search for __mockClaudeUsage in the codebase without type filters
rg -n '__mockClaudeUsage' --glob='!*.spec.ts' --glob='!*.test.ts' -A 2 -B 1

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Find TypeScript files in the UI app
fd '\.(ts|tsx)$' apps/ui --type f | grep -v spec | grep -v test | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1193


🏁 Script executed:

#!/bin/bash
# Search for usage-related imports and fetch logic
rg -n 'usage|Usage|USAGE' apps/ui --glob='!*.spec.ts' --glob='!*.test.ts' -l | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1025


🏁 Script executed:

#!/bin/bash
# Check the auto-mode hook to see how usage is fetched
cat -n apps/ui/src/hooks/use-auto-mode.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 4252


🏁 Script executed:

#!/bin/bash
# Check http-api-client to see usage-related API calls
rg -n 'usage|Usage' apps/ui/src/lib/http-api-client.ts -A 3 -B 1

Repository: AutoMaker-Org/automaker

Length of output: 530


🏁 Script executed:

#!/bin/bash
# Look for any test setup or mock setup in the test file
cat -n apps/ui/tests/features/auto-mode-usage-limit.spec.ts | head -40

Repository: AutoMaker-Org/automaker

Length of output: 1611


🏁 Script executed:

#!/bin/bash
# Read more of the test file to see how API mocking is handled
cat -n apps/ui/tests/features/auto-mode-usage-limit.spec.ts | head -120

Repository: AutoMaker-Org/automaker

Length of output: 4724


🏁 Script executed:

#!/bin/bash
# Search for route interception in the test file
rg -n 'route|intercept|mock|Mock' apps/ui/tests/features/auto-mode-usage-limit.spec.ts

Repository: AutoMaker-Org/automaker

Length of output: 957


🏁 Script executed:

#!/bin/bash
# Check for any test setup files or fixtures that might mock the API
fd -e 'ts' apps/ui/tests --path '*fixture*' --path '*setup*' --path '*util*'

Repository: AutoMaker-Org/automaker

Length of output: 300


🏁 Script executed:

#!/bin/bash
# Check the test utilities for API mocking setup
cat -n apps/ui/tests/utils.ts | head -150

Repository: AutoMaker-Org/automaker

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Search for route interception in all test files
rg -n 'route\.intercept|page\.route|mock|Mock' apps/ui/tests/ -A 2

Repository: AutoMaker-Org/automaker

Length of output: 31608


🏁 Script executed:

#!/bin/bash
# Check electron.ts for API interception or mock setup
cat -n apps/ui/src/lib/electron.ts | head -100

Repository: AutoMaker-Org/automaker

Length of output: 2802


🏁 Script executed:

#!/bin/bash
# Search for where getUsage is called in the app
rg -n 'getUsage' apps/ui/src --type ts --type tsx -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Check test file to see how it navigates - what's the test trying to do?
sed -n '116,130p' apps/ui/tests/features/auto-mode-usage-limit.spec.ts

Repository: AutoMaker-Org/automaker

Length of output: 563


🏁 Script executed:

#!/bin/bash
# Final verification: Check if getUsage is used anywhere in the app code
rg -n 'getUsage\|\.usage\|getClaudeUsage' apps/ui/src -l

Repository: AutoMaker-Org/automaker

Length of output: 49


Fix broken usage mock mechanism in the test.

The mockClaudeUsage helper sets window.__mockClaudeUsage, but this is not integrated with the application. The app fetches usage via HTTP API (/api/claude/usage from http-api-client.ts), and the test does not intercept these API calls. Additionally, navigating via file:// URL prevents HTTP requests from succeeding. Either intercept the API route using Playwright's page.route() to return mocked responses, or use a test server instead of file:// navigation.

🤖 Prompt for AI Agents
In apps/ui/tests/features/auto-mode-usage-limit.spec.ts around lines 33 to 60,
the mockClaudeUsage helper only sets window.__mockClaudeUsage but the app
fetches usage from /api/claude/usage and the test navigates via file:// so HTTP
calls never hit; fix by intercepting the API route with Playwright
page.route('/api/claude/usage', ...) to return a JSON response built from the
MockUsageData (map the same fields used by the app) OR switch the test to use a
local test server (http://...) so the app's fetches succeed and then either
inject the mock on the server route or use route interception; ensure the mock
response shape and status code match the real API.

Comment on lines +116 to +123
test('should show pause dialog when enabling auto mode at usage limit', async ({ page }) => {
await authenticateForTests(page);

// Navigate to the app
await page.goto(`file://${projectPath}`);
await handleLoginScreenIfPresent(page);
await waitForNetworkIdle(page);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for page.goto patterns in E2E tests
rg -n 'page\.goto' apps/ui/tests --type=ts -A1 -B1 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 6553


Fix navigation URL pattern to match established test pattern.

The test uses file://${projectPath} navigation, which deviates from the established pattern across all other tests in the codebase. All other E2E tests navigate using relative paths (e.g., '/', '/board') or explicit http://localhost:3007 URLs. Change to use relative paths like other tests: await page.goto('/');

🤖 Prompt for AI Agents
In apps/ui/tests/features/auto-mode-usage-limit.spec.ts around lines 116 to 123,
the test navigates using an absolute file URL (`file://${projectPath}`) which
deviates from the project's established E2E pattern; change the navigation to
use the relative path pattern used by other tests (e.g., replace the file URL
with await page.goto('/') so the test uses the same server-relative routing and
environment as the rest of the suite).

Comment on lines +139 to +146
if (
!lowerMessage.includes('authentication') &&
!lowerMessage.includes('api key') &&
!lowerMessage.includes('cancelled') &&
!lowerMessage.includes('aborted')
) {
return true;
}
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

Missing "canceled" (American spelling) in exclusion check.

The isCancellationError function checks both "cancelled" and "canceled" spellings, but here only "cancelled" is excluded. For consistency, consider adding the American spelling.

🔎 Proposed fix
     if (
       !lowerMessage.includes('authentication') &&
       !lowerMessage.includes('api key') &&
       !lowerMessage.includes('cancelled') &&
+      !lowerMessage.includes('canceled') &&
       !lowerMessage.includes('aborted')
     ) {
📝 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
if (
!lowerMessage.includes('authentication') &&
!lowerMessage.includes('api key') &&
!lowerMessage.includes('cancelled') &&
!lowerMessage.includes('aborted')
) {
return true;
}
if (
!lowerMessage.includes('authentication') &&
!lowerMessage.includes('api key') &&
!lowerMessage.includes('cancelled') &&
!lowerMessage.includes('canceled') &&
!lowerMessage.includes('aborted')
) {
return true;
}
🤖 Prompt for AI Agents
In libs/utils/src/error-handler.ts around lines 139 to 146, the exclusion check
currently only guards against the British spelling "cancelled" but not the
American "canceled"; update the conditional to also check for
lowerMessage.includes('canceled') so both spellings are excluded consistently
with isCancellationError, keeping the same logical grouping and return behavior.

@Shironex Shironex added the Enhancement Improvements to existing functionality or UI. label Jan 3, 2026
@webdevcody webdevcody changed the base branch from main to v0.11.0rc January 13, 2026 04:58
@webdevcody
Copy link
Collaborator

conflicts

@webdevcody
Copy link
Collaborator

closing from conflicts, reopen later if you want this.

@webdevcody webdevcody closed this Jan 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Improvements to existing functionality or UI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants