-
Notifications
You must be signed in to change notification settings - Fork 527
feat: Add graceful handling for Claude Code usage limits #352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add graceful handling for Claude Code usage limits #352
Conversation
- 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
📝 WalkthroughWalkthroughThis 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
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. Comment |
Summary of ChangesHello @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
🧠 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 AssistThe 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
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 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
|
There was a problem hiding this 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.
| // 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(); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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`; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (api?.claude?.getUsage) { | ||
| const usage = await api.claude.getUsage(); | ||
| if (!('error' in usage)) { | ||
| const isAtLimit = usage.sessionPercentage >= 100 || usage.weeklyPercentage >= 100; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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';| const isAtLimit = usage.sessionPercentage >= 100 || usage.weeklyPercentage >= 100; | |
| const isAtLimit = isClaudeUsageAtLimit(usage); |
There was a problem hiding this 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: AsyncsignalShouldPauseis not awaited, risking unhandled promise rejection.
signalShouldPausewas 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: asyncsignalShouldPausenot 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
mockUsageOKstructure is duplicated in both the integration test file (here) and the unit test file (apps/server/tests/unit/services/auto-mode-service.test.tslines 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
setAutoModePausedandopenAutoModePauseDialogbut 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
fetchingUsagestate 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.waitForor 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
formatTimeRemainingfunction calculates time based onnew 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
autoModeResumeByProjectorprojectschange 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.
setAutoModePausedsets the value tonullwhen clearing, whilesetAutoModeResumeScheduleremoves the key entirely whennullis passed. This inconsistency could lead to accumulatingnullentries inautoModePausedByProjectover 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
📒 Files selected for processing (17)
apps/server/src/services/auto-mode-service.tsapps/server/tests/integration/services/auto-mode-service.integration.test.tsapps/server/tests/unit/services/auto-mode-service-usage-limit.test.tsapps/server/tests/unit/services/auto-mode-service.test.tsapps/ui/src/components/dialogs/auto-mode-resume-dialog.tsxapps/ui/src/components/dialogs/index.tsapps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/ui/src/hooks/use-auto-mode-scheduler.tsapps/ui/src/hooks/use-auto-mode.tsapps/ui/src/routes/__root.tsxapps/ui/src/store/app-store.tsapps/ui/src/types/electron.d.tsapps/ui/tests/features/auto-mode-usage-limit.spec.tsdocs/USAGE_LIMIT_FEATURE_REPORT.mdlibs/utils/src/error-handler.tslibs/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.tsapps/ui/src/components/dialogs/index.tsapps/ui/src/components/dialogs/auto-mode-resume-dialog.tsxapps/ui/src/hooks/use-auto-mode-scheduler.tsapps/ui/src/types/electron.d.tsapps/server/tests/unit/services/auto-mode-service-usage-limit.test.tsapps/server/tests/unit/services/auto-mode-service.test.tsapps/ui/src/routes/__root.tsxlibs/utils/src/error-handler.tsapps/server/src/services/auto-mode-service.tsapps/ui/src/components/views/board-view/board-header.tsxapps/ui/tests/features/auto-mode-usage-limit.spec.tsapps/server/tests/integration/services/auto-mode-service.integration.test.tsapps/ui/src/components/views/board-view.tsxapps/ui/src/store/app-store.tsapps/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.tsapps/ui/src/components/dialogs/index.tsapps/ui/src/components/dialogs/auto-mode-resume-dialog.tsxapps/ui/src/hooks/use-auto-mode-scheduler.tsapps/ui/src/types/electron.d.tsapps/server/tests/unit/services/auto-mode-service-usage-limit.test.tsapps/server/tests/unit/services/auto-mode-service.test.tsapps/ui/src/routes/__root.tsxlibs/utils/src/error-handler.tsapps/server/src/services/auto-mode-service.tsapps/ui/src/components/views/board-view/board-header.tsxapps/ui/tests/features/auto-mode-usage-limit.spec.tsapps/server/tests/integration/services/auto-mode-service.integration.test.tsapps/ui/src/components/views/board-view.tsxapps/ui/src/store/app-store.tsapps/ui/src/hooks/use-auto-mode.ts
apps/server/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
createEventEmitter()fromlib/events.tsfor 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.tsxapps/ui/src/routes/__root.tsxapps/ui/src/components/views/board-view/board-header.tsxapps/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_exhaustedis 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_failuresevent type and extendederrorTypeliterals 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/afterAllsetup is appropriate.apps/ui/src/hooks/use-auto-mode.ts (2)
123-149: LGTM - Clean handling of pause events.The
auto_mode_paused_failureshandler 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
resetTimememo 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
afterEachis 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
buttonelements with properonClickhandlers 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
useAutoModeSchedulerhook 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
useShallowfor 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: VerifyisAmbiguousCLIExitimport path follows shared package convention.Per coding guidelines, imports should use
@automaker/*shared packages. This import from@automaker/utilsappears 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
AutoModeResumeScheduleandAutoModePausedStateare 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) andisAutoModePaused(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:
autoModeResumeByProjectpersists to support scheduled resumes surviving app restarts (per PR objectives).autoModePausedByProjectpersists to maintain "Paused - At Limit" indicator visibility.- Dialog state (
autoModePauseDialogOpen,autoModePauseReason) is intentionally not persisted, preventing the dialog from auto-opening on app launch.
| 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(); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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).
| // Usage limit checks | ||
| claudeUsage, | ||
| isAutoModePaused, | ||
| setAutoModeRunning, | ||
| setAutoModePaused, | ||
| openAutoModePauseDialog, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how often claudeUsage is updated in the codebase
rg -n "setClaudeUsage|claudeUsage" --type ts --type tsx -C 2Repository: 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 15Repository: 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 tsRepository: 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 tsRepository: 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 3Repository: 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.
| autoModeResumeSchedule={useAppStore | ||
| .getState() | ||
| .getAutoModeResumeSchedule(currentProject?.id || '')} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const isAtLimit = usage.sessionPercentage >= 100 || usage.weeklyPercentage >= 100; | ||
| if (isAtLimit) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| if (projectPath) { | ||
| setAutoModePaused(projectPath, { | ||
| pausedAt: new Date().toISOString(), | ||
| reason: 'usage_limit', | ||
| suggestedResumeAt, | ||
| lastKnownUsage: usage, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| // Clear paused state since we're resuming | ||
| if (projectPath) { | ||
| clearAutoModePaused(projectPath); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // 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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -100Repository: 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 1Repository: 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 -20Repository: 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 -20Repository: 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 -100Repository: 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 1Repository: 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 -40Repository: 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 -120Repository: 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.tsRepository: 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 -150Repository: 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 2Repository: 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 -100Repository: 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 2Repository: 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.tsRepository: 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 -lRepository: 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.
| 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); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -100Repository: 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).
| if ( | ||
| !lowerMessage.includes('authentication') && | ||
| !lowerMessage.includes('api key') && | ||
| !lowerMessage.includes('cancelled') && | ||
| !lowerMessage.includes('aborted') | ||
| ) { | ||
| return true; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
|
conflicts |
|
closing from conflicts, reopen later if you want this. |
Summary
This PR adds graceful handling when Claude Code usage limits are reached during auto-mode execution.
Changes
Files Changed
Server (4 files)
apps/server/src/services/auto-mode-service.ts- Added usage limit detection and suggested resume timeapps/server/tests/unit/services/auto-mode-service.test.ts- Added ClaudeUsageService mocksapps/server/tests/unit/services/auto-mode-service-usage-limit.test.ts- New unit testsapps/server/tests/integration/services/auto-mode-service.integration.test.ts- Added mocksUI (9 files)
apps/ui/src/store/app-store.ts- Added autoModePausedByProject stateapps/ui/src/components/views/board-view.tsx- Added proactive usage checksapps/ui/src/components/views/board-view/board-header.tsx- Added pause indicatorapps/ui/src/hooks/use-auto-mode.ts- Handle pause eventsapps/ui/src/hooks/use-auto-mode-scheduler.ts- New scheduler hookapps/ui/src/components/dialogs/auto-mode-resume-dialog.tsx- New dialog componentapps/ui/src/components/dialogs/index.ts- Export new dialogapps/ui/src/types/electron.d.ts- Updated event typesapps/ui/src/routes/__root.tsx- Render dialogE2E Tests (1 file)
apps/ui/tests/features/auto-mode-usage-limit.spec.ts- Comprehensive E2E testsUtils (2 files)
libs/utils/src/error-handler.ts- Added isAmbiguousCLIExit functionlibs/utils/src/index.ts- Export new functionDocumentation (1 file)
docs/USAGE_LIMIT_FEATURE_REPORT.md- Feature documentationTesting
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.