Skip to content

auto-claude: 221-refactor-github-pr-review-with-xstate#1820

Open
AndyMik90 wants to merge 17 commits intodevelopfrom
auto-claude/221-refactor-github-pr-review-with-xstate
Open

auto-claude: 221-refactor-github-pr-review-with-xstate#1820
AndyMik90 wants to merge 17 commits intodevelopfrom
auto-claude/221-refactor-github-pr-review-with-xstate

Conversation

@AndyMik90
Copy link
Owner

@AndyMik90 AndyMik90 commented Feb 14, 2026

Base Branch

  • This PR targets the develop branch (required for all feature/fix PRs)
  • This PR targets main (hotfix only - maintainers)

Description

Refactored GitHub PR review state management from Zustand to XState v5, following the proven architecture established by the task lifecycle system. The new state machine provides formally defined states and transitions, eliminates impossible state combinations, and makes the review lifecycle explicitly visible. All existing PR review functionality is preserved while the underlying architecture now supports future enhancements like retry logic and timeout handling.

Related Issue

Closes #221

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 📚 Documentation
  • ♻️ Refactor
  • 🧪 Test

Area

  • Frontend
  • Backend
  • Fullstack

Commit Message Format

Follow conventional commits: <type>: <subject>

Types: feat, fix, docs, style, refactor, test, chore

Example: feat: add user authentication system

AI Disclosure

  • This PR includes AI-generated code (Claude, Codex, Copilot, etc.)

Tool(s) used: Claude Code

Testing level:

  • Untested -- AI output not yet verified

  • Lightly tested -- ran the app / spot-checked key paths

  • Fully tested -- all tests pass, manually verified behavior

  • I understand what this PR does and how the underlying code works

Checklist

  • I've synced with develop branch
  • I've tested my changes locally
  • I've followed the code principles (SOLID, DRY, KISS)
  • My PR is small and focused (< 400 lines ideally)

Platform Testing Checklist

CRITICAL: This project supports Windows, macOS, and Linux. Platform-specific bugs are a common source of breakage.

  • Windows tested (either on Windows or via CI)
  • macOS tested (either on macOS or via CI)
  • Linux tested (CI covers this)
  • Used centralized platform/ module instead of direct process.platform checks
  • No hardcoded paths (used findExecutable() or platform abstractions)

If you only have access to one OS: CI now tests on all platforms. Ensure all checks pass before submitting.

CI/Testing Requirements

  • All CI checks pass on all platforms (Windows, macOS, Linux)
  • All existing tests pass
  • New features include test coverage
  • Bug fixes include regression tests

Screenshots

N/A - No UI changes

Feature Toggle

  • Behind localStorage flag: use_feature_name
  • Behind settings toggle
  • Behind environment variable/config
  • N/A - Feature is complete and ready for all users

Breaking Changes

Breaking: No

Details: All changes are internal architectural refactoring with preserved API surface. The Zustand store interface remains compatible; changes are transparent to consumers through IPC event flow.

Summary by CodeRabbit

  • New Features

    • Centralized PR review state manager with unified main→renderer state-change events and external-review completion notification; new IPC channels and preload API for notifyExternalReviewComplete and onPRReviewStateChange.
    • Renderer now notifies main of external review completion; auth changes reset PR review state.
  • Refactor

    • PR review flows moved to main-process state-machine handling; renderer delegates control and receives unified state updates.
    • PR review store API consolidated (new setLoadedReviewResult/handlePRReviewStateChange; legacy start/progress/result helpers removed).
  • Tests

    • Extensive unit and integration tests for the state machine, manager, utilities, and IPC wiring.

AndyMik90 and others added 10 commits February 14, 2026 00:39
…nd state utils

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…review state machine and utilities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ron main process

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
File: apps/frontend/src/main/__tests__/pr-review-state-manager.test.ts
…teManager

Replace all 6 createIPCCommunicators() calls for review lifecycle events with
PRReviewStateManager routing. The manager's XState actors now handle all state
transitions and emit on GITHUB_PR_REVIEW_STATE_CHANGE channel.

Changes:
- Create PRReviewStateManager instance in registerPRHandlers()
- GITHUB_PR_REVIEW handler: route start/progress/complete/error through manager
- GITHUB_PR_FOLLOWUP_REVIEW handler: route through manager with previous result
- GITHUB_PR_REVIEW_CANCEL handler: call manager.handleCancel() after kill
- sendReviewStateUpdate(): use manager.handleComplete() instead of direct IPC
- runPRReview(): accept manager param, use for progress callbacks
- Remove unused createIPCCommunicators import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…state subscriber

Replace 6 imperative review lifecycle actions with a single handlePRReviewStateChange
handler that maps XState state/context to the existing PRReviewState interface shape.
Update initializePRReviewListeners to use onPRReviewStateChange IPC channel. Remove
exported startPRReview/startFollowupReview helpers (now direct IPC calls from hooks).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iven architecture

- Remove imports of deleted startPRReview/startFollowupReview store functions
- runReview/runFollowupReview now call IPC directly (main process handles XState)
- cancelReview no longer mutates store directly (state flows back via IPC)
- Add setLoadedReviewResult action to pr-review-store for disk-loaded reviews
- Replace all setPRReviewResult calls with setLoadedReviewResult

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove obsolete setPRReviewResult calls in PRDetail.tsx (now handled by XState)
- Add missing onPRReviewStateChange to browser-mock.ts
- Fix pr-review-machine.test.ts mock data to match actual PRReviewResult/PRReviewProgress types
- All 130 test files pass (3278 tests), typecheck clean, lint clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…p transition (qa-requested)

Fixes:
- Build PRReviewStatePayload object in emitStateToRenderer instead of sending raw args
- Wire up handleAuthChange via ipcMain listener for GITHUB_AUTH_CHANGED
- Add START_FOLLOWUP_REVIEW transition to error state in pr-review-machine
- Update state manager tests to verify PRReviewStatePayload shape

Verified:
- All 3279 tests pass
- TypeScript compilation clean

QA Fix Session: 1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added area/frontend This is frontend only size/XL Extra large (1000+ lines) labels Feb 14, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes PR review lifecycle in a new main-process PRReviewStateManager backed by an XState prReviewMachine; routes start/progress/complete/error/cancel/followup through the manager, adds IPC channels and preload API hooks, and updates renderer store/components and tests to consume unified PRReviewStatePayload events.

Changes

Cohort / File(s) Summary
State machine core
apps/frontend/src/shared/state-machines/pr-review-machine.ts, apps/frontend/src/shared/state-machines/pr-review-state-utils.ts, apps/frontend/src/shared/state-machines/index.ts
Add XState prReviewMachine, context/event types, state-name utilities, and re-exports.
State machine tests
apps/frontend/src/shared/state-machines/__tests__/pr-review-machine.test.ts, apps/frontend/src/shared/state-machines/__tests__/pr-review-state-utils.test.ts
New unit tests covering machine transitions, progress/result/error, follow-ups, external review detection, and legacy mappings.
Main-process state manager & tests
apps/frontend/src/main/pr-review-state-manager.ts, apps/frontend/src/main/__tests__/pr-review-state-manager.test.ts
Introduce PRReviewStateManager that manages per-PR XState actors, deduplicates snapshots, emits PRReviewStatePayload via IPC, and provides start/progress/complete/error/cancel/followup/clear APIs; comprehensive tests for lifecycle, routing, dedupe, cleanup, and concurrency.
PR handlers integration
apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts
Refactor PR handlers to use PRReviewStateManager for runPRReview, sendReviewStateUpdate, and to route progress/complete/error/cancel/followup through manager methods; add IPC handler for external-complete channel.
Auth IPC change
apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts
Also emit GITHUB_AUTH_CHANGED on ipcMain (in addition to renderer windows) so main-process listeners can react to auth changes.
Preload API & IPC constants
apps/frontend/src/preload/api/modules/github-api.ts, apps/frontend/src/shared/constants/ipc.ts
Expose notifyExternalReviewComplete and onPRReviewStateChange on preload API; export PRReviewStatePayload type; add IPC channels GITHUB_PR_REVIEW_STATE_CHANGE and GITHUB_PR_NOTIFY_EXTERNAL_REVIEW_COMPLETE.
Renderer store refactor
apps/frontend/src/renderer/stores/github/pr-review-store.ts, apps/frontend/src/renderer/stores/github/index.ts
Replace granular start/progress/result/error APIs with unified handlePRReviewStateChange, add setLoadedReviewResult, remove public startPRReview/startFollowupReview, and switch listeners to single onPRReviewStateChange path.
Renderer surface updates
apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx, apps/frontend/src/renderer/components/github-prs/hooks/useGitHubPRs.ts
Delegate external-review completion/timeouts and review start/followup/cancel flows to main-process IPC (window.electronAPI.github.*); use setLoadedReviewResult for loaded results; remove local store-driven start/cancel flows.
Browser mock & test harness
apps/frontend/src/renderer/lib/browser-mock.ts, apps/frontend/src/main/__tests__/pr-review-state-manager.test.ts
Add notifyExternalReviewComplete and onPRReviewStateChange to browser mock; add/update tests to exercise new IPC and manager behavior.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Renderer
    participant Preload as Preload/API
    participant Main as Main Process
    participant StateMgr as PRReviewStateManager
    participant XActor as XState Actor

    User->>Renderer: Click "Start Review"
    Renderer->>Preload: window.electronAPI.github.runPRReview(prNumber)
    Preload->>Main: IPC runPRReview
    Main->>StateMgr: handleStartReview(key, prNumber, projectId)
    StateMgr->>XActor: create & start actor (START_REVIEW)
    XActor-->>StateMgr: snapshot (progress/result/error)
    StateMgr->>Main: safeSendToRenderer(GITHUB_PR_REVIEW_STATE_CHANGE, payload)
    Main->>Preload: deliver IPC event
    Preload->>Renderer: onPRReviewStateChange(key, payload)
    Renderer->>Renderer: store.handlePRReviewStateChange(key, payload)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

refactor

Suggested reviewers

  • AlexMadera

Poem

🐰 I hopped through branches and stitched each state with care,

Actors hum progress and IPC fills the air,
From idle to review, then completed with cheer,
I nibble at race conditions and emit updates near,
Hop, emit, repeat — the UI listens, I swear! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 4
❌ Failed checks (3 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR refactors GitHub PR review state management with XState, but the linked issue #221 is about Python subprocess hangs on macOS. These are fundamentally unrelated topics. Verify that the correct issue is linked. The PR appears to address PR review state management, not Python subprocess import hangs. Link the appropriate issue or clarify the relationship.
Out of Scope Changes check ⚠️ Warning The PR implements extensive GitHub PR review state management refactoring (XState machine, state manager, IPC changes, store updates) which is completely out of scope for issue #221 about Python subprocess hangs. Either relink this PR to the correct issue about GitHub PR review state management, or create a separate PR for the XState refactor with its proper issue reference.
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'auto-claude: 221-refactor-github-pr-review-with-xstate' is vague and generic; it mentions an issue number and topic but doesn't clearly describe the main change. Revise the title to clearly describe the main change, such as 'Refactor GitHub PR review state management from Zustand to XState machine' or similar.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch auto-claude/221-refactor-github-pr-review-with-xstate

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 @AndyMik90, 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 significantly enhances the stability and maintainability of the GitHub PR review feature by migrating its state management to XState. By centralizing the complex state logic in the main process and formalizing state transitions, the system becomes more predictable and less prone to errors. This architectural shift simplifies the renderer's responsibilities, allowing it to react to a single, comprehensive state stream, and lays the groundwork for advanced features without disrupting current functionality.

Highlights

  • XState Refactoring: The core state management for GitHub PR reviews has been refactored from Zustand to XState v5, adopting a robust state machine architecture similar to the task lifecycle system.
  • Formalized State Management: This refactoring introduces formally defined states and transitions, eliminating impossible state combinations and making the PR review lifecycle explicitly visible and predictable.
  • Main Process Centralization: PR review state logic is now centralized in the main Electron process via a new PRReviewStateManager, which manages individual XState actors for each PR review.
  • Simplified Renderer Logic: The renderer process no longer directly manipulates PR review state in Zustand. Instead, it dispatches actions to the main process, which then updates the state machine and emits a single GITHUB_PR_REVIEW_STATE_CHANGE IPC event back to the renderer.
  • Enhanced IPC Communication: A new IPC channel, github:pr:reviewStateChange, has been introduced to stream comprehensive PR review state payloads from the main process to the renderer, replacing separate progress, complete, and error channels.
  • Preserved Functionality & Future-Proofing: All existing PR review functionality is preserved, while the new architecture provides a solid foundation for future enhancements such as retry logic and timeout handling.
  • Comprehensive Testing: New unit tests have been added for both the PRReviewStateManager and the prReviewMachine to ensure the correctness and reliability of the new state management system.
Changelog
  • apps/frontend/src/main/tests/pr-review-state-manager.test.ts
    • Added new test file for PRReviewStateManager to verify its lifecycle, event routing, state emission, deduplication, and cleanup logic.
  • apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts
    • Modified sendAuthChangedToRenderer to emit GITHUB_AUTH_CHANGED on ipcMain to allow main-process listeners, such as PRReviewStateManager, to react to authentication changes.
  • apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts
    • Removed direct usage of createIPCCommunicators for PR review progress, completion, and error events.
    • Introduced and integrated PRReviewStateManager to manage PR review state transitions and IPC emissions.
    • Updated runPRReview and sendReviewStateUpdate functions to accept and utilize the prReviewStateManager instance.
    • Modified registerPRHandlers to instantiate PRReviewStateManager and subscribe it to GITHUB_AUTH_CHANGED events for cleanup.
    • Refactored runPRReview to use prReviewStateManager.handleProgress for progress updates.
    • Updated runPRReview to use prReviewStateManager.handleComplete for review completion, including external review detection.
    • Modified error handling in runPRReview and runFollowupReview to use prReviewStateManager.handleError.
    • Added calls to prReviewStateManager.handleStartReview and prReviewStateManager.handleStartFollowupReview when reviews are initiated.
    • Integrated prReviewStateManager.handleCancel into the review cancellation logic.
  • apps/frontend/src/main/pr-review-state-manager.ts
    • Added new class PRReviewStateManager to encapsulate XState actor management for PR reviews.
    • Implemented methods to handle various PR review events (handleStartReview, handleProgress, handleComplete, handleError, handleCancel, handleClearReview, handleAuthChange).
    • Included logic to create and manage XState actors per PR, subscribe to their state changes, and emit updates to the renderer via safeSendToRenderer.
    • Implemented state deduplication to prevent redundant IPC emissions for identical state-context combinations.
  • apps/frontend/src/preload/api/modules/github-api.ts
    • Added onPRReviewStateChange to the GitHubAPI interface for listening to XState-driven state updates.
    • Defined PRReviewStatePayload interface to represent the comprehensive state object emitted by the XState machine.
    • Implemented onPRReviewStateChange in createGitHubAPI to create an IPC listener for the new state change channel.
  • apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx
    • Removed direct Zustand updates for external review polling timeouts and completion, now relying on XState-driven state changes received via IPC.
  • apps/frontend/src/renderer/components/github-prs/hooks/useGitHubPRs.ts
    • Removed direct imports and usage of storeStartPRReview and storeStartFollowupReview from the Zustand store.
    • Updated startPRReview and startFollowupReview functions to directly call window.electronAPI.github.runPRReview and runFollowupReview respectively, delegating state management to the main process.
    • Modified cancelPRReview to remove direct Zustand error setting, as XState now handles the state transition and error emission.
    • Renamed setPRReviewResult calls to setLoadedReviewResult when loading existing review results from disk.
  • apps/frontend/src/renderer/lib/browser-mock.ts
    • Updated browserMockAPI to include a mock implementation for onPRReviewStateChange.
  • apps/frontend/src/renderer/stores/github/index.ts
    • Removed exports for startPRReview and startFollowupReview as these actions are now handled in the main process.
  • apps/frontend/src/renderer/stores/github/pr-review-store.ts
    • Added PRReviewStatePayload type import.
    • Removed Zustand actions: startPRReview, startFollowupReview, setPRReviewProgress, setPRReviewResult, setPRReviewError, and setExternalReviewInProgress.
    • Introduced handlePRReviewStateChange action to process incoming PRReviewStatePayload from the main process, mapping XState context to the local store state.
    • Renamed setPRReviewResult to setLoadedReviewResult to distinguish it from XState-driven updates and added a guard to prevent overwriting active XState reviews.
    • Updated initializePRReviewListeners to listen for the new onPRReviewStateChange IPC event, replacing individual progress, complete, and error listeners.
  • apps/frontend/src/shared/constants/ipc.ts
    • Added new IPC channel GITHUB_PR_REVIEW_STATE_CHANGE for comprehensive PR review state updates.
  • apps/frontend/src/shared/state-machines/tests/pr-review-machine.test.ts
    • Added new test file containing comprehensive unit tests for the prReviewMachine XState state machine, covering various state transitions, context updates, and guards.
  • apps/frontend/src/shared/state-machines/tests/pr-review-state-utils.test.ts
    • Added new test file for pr-review-state-utils to verify state name constants, settled states, and legacy mapping function.
  • apps/frontend/src/shared/state-machines/index.ts
    • Exported prReviewMachine, PRReviewContext, PRReviewEvent, PR_REVIEW_STATE_NAMES, PR_REVIEW_SETTLED_STATES, mapPRReviewStateToLegacy, and PRReviewStateName.
  • apps/frontend/src/shared/state-machines/pr-review-machine.ts
    • Added new XState machine definition prReviewMachine for managing the PR review lifecycle.
    • Defined PRReviewContext to hold PR-specific data like number, project ID, progress, result, and error.
    • Defined PRReviewEvent types for all possible actions and transitions within the machine.
    • Implemented states: idle, reviewing, externalReview, completed, and error.
    • Configured state transitions with appropriate actions to update context and guards to prevent invalid transitions.
  • apps/frontend/src/shared/state-machines/pr-review-state-utils.ts
    • Added new utility file for PR review state, including PR_REVIEW_STATE_NAMES (all state names), PR_REVIEW_SETTLED_STATES (terminal states), and mapPRReviewStateToLegacy for backward compatibility.
Activity
  • The pull request introduces a significant refactoring of the PR review state management, moving from Zustand to XState v5.
  • The changes include new features like formalized state transitions and improved error handling.
  • The author indicates that AI-generated code was used, specifically from Claude Code.
  • The changes have been fully tested, with all tests passing and behavior manually verified.
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 is an excellent and comprehensive refactoring of the PR review state management. Migrating from Zustand to XState brings more formality, predictability, and robustness to the state transitions, which is a great improvement for a complex feature like this. The new PRReviewStateManager successfully centralizes the logic that was previously scattered, and the use of a single IPC channel for state changes simplifies the communication between the main and renderer processes. The addition of thorough unit tests for the new state machine and manager is also a fantastic contribution that increases confidence in this new architecture.

I've found a couple of minor issues related to state handling during cleanup and when loading results from disk, which I've commented on. Once those are addressed, this will be a very solid architectural improvement.

Comment on lines +154 to +166
const payload: PRReviewStatePayload = {
state: stateValue,
prNumber: ctx?.prNumber ?? 0,
projectId: ctx?.projectId ?? '',
isReviewing: stateValue === 'reviewing' || stateValue === 'externalReview',
startedAt: ctx?.startedAt ?? null,
progress: ctx?.progress ?? null,
result: ctx?.result ?? null,
previousResult: ctx?.previousResult ?? null,
error: ctx?.error ?? null,
isExternalReview: ctx?.isExternalReview ?? false,
isFollowup: ctx?.isFollowup ?? false,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

high

When emitting a cleared state (where snapshot is null), such as in handleAuthChange, the prNumber and projectId in the payload are incorrectly defaulted to 0 and ''. This can lead to incorrect data being set in the renderer's store for the given PR key. The key for the PR, which is available, should be parsed to provide the correct projectId and prNumber in the payload for these cleared states.

Suggested change
const payload: PRReviewStatePayload = {
state: stateValue,
prNumber: ctx?.prNumber ?? 0,
projectId: ctx?.projectId ?? '',
isReviewing: stateValue === 'reviewing' || stateValue === 'externalReview',
startedAt: ctx?.startedAt ?? null,
progress: ctx?.progress ?? null,
result: ctx?.result ?? null,
previousResult: ctx?.previousResult ?? null,
error: ctx?.error ?? null,
isExternalReview: ctx?.isExternalReview ?? false,
isFollowup: ctx?.isFollowup ?? false,
};
const [pId, prNumStr] = key.split(':');
const payload: PRReviewStatePayload = {
state: stateValue,
prNumber: ctx?.prNumber ?? (prNumStr ? parseInt(prNumStr, 10) : 0),
projectId: ctx?.projectId ?? pId,
isReviewing: stateValue === 'reviewing' || stateValue === 'externalReview',
startedAt: ctx?.startedAt ?? null,
progress: ctx?.progress ?? null,
result: ctx?.result ?? null,
previousResult: ctx?.previousResult ?? null,
error: ctx?.error ?? null,
isExternalReview: ctx?.isExternalReview ?? false,
isFollowup: ctx?.isFollowup ?? false,
};

error: result.error ?? null,
// Clear new commits check when review completes (it was just reviewed)
// BUT preserve it during preload/refresh to avoid race condition
error: null,
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

When loading a review result from disk using setLoadedReviewResult, any existing error information in the result is being discarded because error is hardcoded to null. This could hide important error information from previous failed reviews. The error from the loaded result should be preserved.

Suggested change
error: null,
error: result.error ?? null,

Copy link
Contributor

@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: 14

Caution

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

⚠️ Outside diff range comments (1)
apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx (1)

413-448: ⚠️ Potential issue | 🟠 Major

Verify that external review completion detection is now properly handled by the backend state manager.

After the refactor, the polling logic at lines 422–437 no longer updates local state—it just logs and relies on the XState/IPC pathway. Two potential issues exist:

  1. Timeout without interval cleanup (lines 422–425): When the 30-minute timeout expires, pollForCompletion returns early, but setInterval (line 446) continues firing every 3 seconds. Each call immediately returns without action, wasting resources.

  2. Completion detection shifted to backend (lines 430–437): The polling was the primary mechanism for detecting when external reviews complete. It now logs but doesn't update state. If the backend PRReviewStateManager doesn't reliably emit completion events via IPC, external reviews will remain stuck in "reviewing" state with no UI feedback.

Before merging, confirm:

  • The backend emits completion events for external reviews via the established IPC channel.
  • These events are being received and processed by the component or state machine.
  • Timeout handling is accounted for in the state machine (the current polling has no timeout recovery).

If these aren't in place, either remove the polling entirely or restore the store update as a fallback.

🤖 Fix all issues with AI agents
In `@apps/frontend/src/main/__tests__/pr-review-state-manager.test.ts`:
- Around line 1-3: The test imports use relative paths; update them to use your
tsconfig path aliases: replace import of PRReviewStateManager from
'../pr-review-state-manager' with the alias-based path (e.g.,
'@/main/pr-review-state-manager' or the project’s equivalent) and replace the
type import of PRReviewResult and PRReviewProgress from
'../../preload/api/modules/github-api' with the preload alias
'@preload/api/modules/github-api'; ensure the imported symbols
PRReviewStateManager, PRReviewResult, and PRReviewProgress remain unchanged so
the test compiles with the configured aliases.
- Around line 15-30: The mock factories createMockProgress and createMockResult
produce payloads that don't match the real PRReviewProgress/PRReviewResult
shape; update createMockResult to use overallStatus: "approve" (not "approved")
and ensure all required fields on PRReviewResult (e.g., any status enums,
reviewer lists, timestamps, or IDs) are included, and update createMockProgress
to populate all required PRReviewProgress fields (phase, progress, message and
any required metadata like startedAt/endedAt or file counts) so tests exercise
realistic payloads; locate and modify createMockProgress and createMockResult
accordingly to mirror the real types used by the codebase.

In `@apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts`:
- Around line 54-55: The ipcMain.emit call is currently discarding the
GitHubAuthChangedPayload because listeners follow the (_, arg1, ...) signature;
update the emit to pass the payload as the second argument
(ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, payload)) and/or update the
listener in pr-handlers.ts to accept the payload parameter (change listener
signature to (_, payload: GitHubAuthChangedPayload) => ...) so the payload isn't
silently ignored; alternatively consider replacing this ipcMain internal signal
with a dedicated Node EventEmitter for main-process-only events (preferred) and
move handlers to subscribe to that emitter instead of ipcMain.

In `@apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts`:
- Around line 29-30: Replace the relative import of PRReviewStateManager with
the project path-alias form: find the import statement that currently references
"../../pr-review-state-manager" and change it to use the frontend alias (e.g.
"@/pr-review-state-manager" or the appropriate alias from tsconfig) so
PRReviewStateManager is imported via the configured path alias rather than a
relative path; update any sibling imports in the same file to match the alias
convention if present.

In `@apps/frontend/src/main/pr-review-state-manager.ts`:
- Around line 143-145: getKey currently concatenates projectId and prNumber with
":" which breaks if projectId can contain ":"; update the getKey function to
produce an unambiguous key (for example encode projectId with JSON.stringify or
use a null-character delimiter `\0`, or return a structured string like
`${Buffer.from(projectId).toString('base64')}:${prNumber}`) and update any
consumers that parse the key (e.g., emitStateToRenderer or any reverse-parsing
logic) to use the same decoding approach so keys round-trip reliably; modify
getKey and the corresponding parsing/lookup code paths (getKey and
emitStateToRenderer) to use the chosen safe encoding/delimiter.
- Around line 118-137: getOrCreateActor currently leaves actors running forever;
modify the actor.subscribe callback in getOrCreateActor (the actor created via
createActor(prReviewMachine)) to detect terminal states (e.g., when stateValue
=== 'completed' or 'error' or using snapshot.matches if available), emit the
final snapshot via emitStateToRenderer(key, snapshot) if not already emitted,
then call actor.stop(), remove the actor from this.actors
(this.actors.delete(key)), and clean up any bookkeeping like
this.lastStateByPR.delete(key) so settled actors are removed automatically;
ensure you still perform the existing dedup check before cleanup to avoid
double-emitting.
- Around line 90-98: handleAuthChange currently calls emitStateToRenderer(key,
null) which creates a null payload with prNumber: 0 and projectId: '',
corrupting stored PRReviewState; update handleAuthChange to parse the composite
key (e.g., "projectId:prNumber") to extract projectId and prNumber for each
actor and pass a null-snapshot payload that preserves those identifiers when
calling emitStateToRenderer(key, /* null payload with projectId and prNumber
*/). Locate handleAuthChange and emitStateToRenderer usages to ensure the
emitted object contains the extracted projectId and prNumber instead of relying
on defaults.

In `@apps/frontend/src/renderer/stores/github/pr-review-store.ts`:
- Around line 2-7: Replace the relative preload import with the project path
alias: update the import that currently pulls PRReviewProgress, PRReviewResult,
NewCommitsCheck, PRReviewStatePayload from
'../../../preload/api/modules/github-api' to use the tsconfig path alias (e.g.
'@preload/api/modules/github-api'); ensure the import specifiers
(PRReviewProgress, PRReviewResult, NewCommitsCheck, PRReviewStatePayload) remain
unchanged so code and types continue to resolve.
- Around line 83-113: The handler handlePRReviewStateChange is overwriting valid
prNumber/projectId with empty values from PRReviewStatePayload when the state
machine emits an idle/cleared payload; update the logic to detect empty payloads
(e.g., payload.prNumber or payload.projectId falsy) and instead either remove
the entry from prReviews (delete state.prReviews[key]) or preserve the existing
identifiers by falling back to existing.prNumber/existing.projectId or using the
key as a fallback identifier before writing the updated PRReviewState; ensure
you reference PRReviewState, PRReviewStatePayload, prReviews and key when
applying this fix so selectors and follow-up updates aren’t corrupted.

In `@apps/frontend/src/shared/state-machines/__tests__/pr-review-machine.test.ts`:
- Around line 1-3: Replace the relative import of the state machine in the test
with the configured path alias: change the import that brings in prReviewMachine
and PRReviewEvent (currently from '../pr-review-machine') to use the tsconfig
alias for shared state-machines (e.g.,
'@shared/state-machines/pr-review-machine'); update the import line in the test
so createActor and the machine types still reference prReviewMachine and
PRReviewEvent but via the alias.

In `@apps/frontend/src/shared/state-machines/pr-review-machine.ts`:
- Around line 1-2: Replace the relative import of the API types with the
tsconfig path alias: change the import that brings in PRReviewProgress and
PRReviewResult from '../../preload/api/modules/github-api' to use the `@preload`
alias (import the same types from '@preload/api/modules/github-api') in
pr-review-machine.ts so the frontend follows the project's path-alias
conventions.
- Around line 99-113: The machine's completed state ignores the REVIEW_COMPLETE
event so updates emitted by sendReviewStateUpdate() (e.g., postedAt,
hasPostedFindings) never update the UI; modify the completed state's event map
to handle REVIEW_COMPLETE and run an action that merges the incoming review
payload into machine context (e.g., add an event handler REVIEW_COMPLETE: {
actions: 'applyReviewUpdate' } or similar), implementing the action
(applyReviewUpdate / refreshContextInPlace) to update fields like postedAt and
hasPostedFindings without leaving the completed state so post-operation updates
propagate to the UI.

In `@apps/frontend/src/shared/state-machines/pr-review-state-utils.ts`:
- Around line 25-27: The exported PR_REVIEW_SETTLED_STATES is typed as
ReadonlySet<string> but is populated with PRReviewStateName values; narrow its
type to ReadonlySet<PRReviewStateName> to catch typos and enforce known state
names (change the declaration of PR_REVIEW_SETTLED_STATES accordingly), and
update any consumers that currently pass plain string snapshots (e.g.,
snapshot.value) to either cast to PRReviewStateName or validate/convert before
lookup so types remain consistent.
- Around line 37-51: The function mapPRReviewStateToLegacy currently returns
'idle' for unknown states which can hide new-state regressions; update the
default branch in mapPRReviewStateToLegacy to log a warning (e.g., using
console.warn or process.env.NODE_ENV check to only warn in development) that
includes the unexpected state value and the target type LegacyPRReviewStatus so
maintainers see unmapped states early; keep the fallback return 'idle' for
compatibility but ensure the warning message identifies the function name and
the unmapped state.

Comment on lines +1 to +3
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { PRReviewStateManager } from '../pr-review-state-manager';
import type { PRReviewResult, PRReviewProgress } from '../../preload/api/modules/github-api';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Switch test imports to path aliases.
Use the configured aliases for main/preload imports.

🔧 Suggested change
-import { PRReviewStateManager } from '../pr-review-state-manager';
-import type { PRReviewResult, PRReviewProgress } from '../../preload/api/modules/github-api';
+import { PRReviewStateManager } from '@/main/pr-review-state-manager';
+import type { PRReviewResult, PRReviewProgress } from '@preload/api/modules/github-api';
As per coding guidelines: Frontend code must use path aliases defined in tsconfig.json (`@/*`, `@shared/*`, `@preload/*`, `@features/*`, `@components/*`, `@hooks/*`, `@lib/*`).
📝 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
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { PRReviewStateManager } from '../pr-review-state-manager';
import type { PRReviewResult, PRReviewProgress } from '../../preload/api/modules/github-api';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { PRReviewStateManager } from '@/main/pr-review-state-manager';
import type { PRReviewResult, PRReviewProgress } from '@preload/api/modules/github-api';
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/__tests__/pr-review-state-manager.test.ts` around
lines 1 - 3, The test imports use relative paths; update them to use your
tsconfig path aliases: replace import of PRReviewStateManager from
'../pr-review-state-manager' with the alias-based path (e.g.,
'@/main/pr-review-state-manager' or the project’s equivalent) and replace the
type import of PRReviewResult and PRReviewProgress from
'../../preload/api/modules/github-api' with the preload alias
'@preload/api/modules/github-api'; ensure the imported symbols
PRReviewStateManager, PRReviewResult, and PRReviewProgress remain unchanged so
the test compiles with the configured aliases.

Comment on lines +15 to +30
function createMockProgress(overrides: Partial<PRReviewProgress> = {}): PRReviewProgress {
return {
phase: 'analyzing',
progress: 50,
message: 'Analyzing files...',
...overrides
} as PRReviewProgress;
}

function createMockResult(overrides: Partial<PRReviewResult> = {}): PRReviewResult {
return {
overallStatus: 'approved',
summary: 'Looks good',
...overrides
} as PRReviewResult;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Mock PRReviewResult/PRReviewProgress should match the real shape.
overallStatus should use "approve" and required fields should be populated so tests exercise realistic payloads.

🧪 Suggested fix
 function createMockProgress(overrides: Partial<PRReviewProgress> = {}): PRReviewProgress {
   return {
+    prNumber: overrides.prNumber ?? 42,
     phase: 'analyzing',
     progress: 50,
     message: 'Analyzing files...',
     ...overrides
   } as PRReviewProgress;
 }
 
 function createMockResult(overrides: Partial<PRReviewResult> = {}): PRReviewResult {
   return {
-    overallStatus: 'approved',
+    prNumber: overrides.prNumber ?? 42,
+    repo: 'test/repo',
+    success: true,
+    findings: [],
     summary: 'Looks good',
+    overallStatus: 'approve',
+    reviewedAt: new Date().toISOString(),
     ...overrides
   } as PRReviewResult;
 }
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/__tests__/pr-review-state-manager.test.ts` around
lines 15 - 30, The mock factories createMockProgress and createMockResult
produce payloads that don't match the real PRReviewProgress/PRReviewResult
shape; update createMockResult to use overallStatus: "approve" (not "approved")
and ensure all required fields on PRReviewResult (e.g., any status enums,
reviewer lists, timestamps, or IDs) are included, and update createMockProgress
to populate all required PRReviewProgress fields (phase, progress, message and
any required metadata like startedAt/endedAt or file counts) so tests exercise
realistic payloads; locate and modify createMockProgress and createMockResult
accordingly to mirror the real types used by the codebase.

Comment on lines 54 to 55
// Emit on ipcMain so main-process listeners (e.g., PRReviewStateManager) can react
ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, payload);
Copy link
Contributor

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:

rg -n "GITHUB_AUTH_CHANGED" --type=ts --type=tsx -B 2 -A 8

Repository: AndyMik90/Auto-Claude

Length of output: 92


🏁 Script executed:

rg -n "ipcMain\.on.*GITHUB_AUTH_CHANGED" --type=ts --type=tsx -A 5

Repository: AndyMik90/Auto-Claude

Length of output: 92


🏁 Script executed:

# Find the PRReviewStateManager to see how it handles the auth change event
fd -e ts -e tsx "pr.*review.*state.*manager" -o -name "*PRReviewStateManager*"

Repository: AndyMik90/Auto-Claude

Length of output: 295


🏁 Script executed:

# Also look for any other references to how this event is used
rg -n "ipcMain\.emit" --type=ts -A 2 | head -50

Repository: AndyMik90/Auto-Claude

Length of output: 318


🏁 Script executed:

rg "GITHUB_AUTH_CHANGED" --type ts -B 2 -A 8

Repository: AndyMik90/Auto-Claude

Length of output: 5706


🏁 Script executed:

# Search for ipcMain.on listeners for this event
rg "ipcMain\.on.*GITHUB_AUTH_CHANGED" --type ts -A 5

Repository: AndyMik90/Auto-Claude

Length of output: 637


🏁 Script executed:

# Find PRReviewStateManager
fd "PRReviewStateManager|pr.*review" --type f

Repository: AndyMik90/Auto-Claude

Length of output: 678


🏁 Script executed:

# Also check what IPC_CHANNELS.GITHUB_AUTH_CHANGED is defined as
rg "GITHUB_AUTH_CHANGED.*=" --type ts -B 1 -A 1

Repository: AndyMik90/Auto-Claude

Length of output: 410


🏁 Script executed:

# Look at the full pr-handlers.ts to see the context of the listener
rg -A 20 "ipcMain.on\(IPC_CHANNELS.GITHUB_AUTH_CHANGED" apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 880


🏁 Script executed:

# Check if there are any other ipcMain.on listeners that DO take event/data parameters
rg "ipcMain\.on\(" apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts -A 3

Repository: AndyMik90/Auto-Claude

Length of output: 593


🏁 Script executed:

# Check Node EventEmitter documentation pattern in codebase - any other emit calls?
rg "ipcMain\.emit\(" --type ts -B 2 -A 2

Repository: AndyMik90/Auto-Claude

Length of output: 521


🏁 Script executed:

# Check if there's any documentation or tests showing how ipcMain.on/emit should work
fd -e test ".*ipc" --type f

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

# Look at how ipcMain is imported and used elsewhere
rg "ipcMain\.on\|ipcMain\.emit" --type ts -B 1 -A 1 | head -100

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

# Check if there are other examples of (_, arg) pattern vs () pattern in similar handlers
rg "ipcMain\.on\(" apps/frontend/src/main/ipc-handlers/ --type ts -A 2 -B 1

Repository: AndyMik90/Auto-Claude

Length of output: 11315


🏁 Script executed:

# Check how createIpcListener works in the preload to understand the full flow
cat apps/frontend/src/preload/api/modules/github-api.ts | head -50

Repository: AndyMik90/Auto-Claude

Length of output: 1291


🏁 Script executed:

# Look at how the payload is used in the renderer listener callback
rg "onGitHubAuthChanged\|GITHUB_AUTH_CHANGED" apps/frontend/src/renderer --type ts -B 2 -A 5

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

# Search for any other listeners for GITHUB_AUTH_CHANGED
rg "GITHUB_AUTH_CHANGED" apps/frontend/src --type ts -B 3 -A 3

Repository: AndyMik90/Auto-Claude

Length of output: 3755


🏁 Script executed:

# Look at how ipcMain.emit is documented/used - check if there are tests
find apps/frontend/src/main/__tests__ -name "*.ts" -type f | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 1226


🏁 Script executed:

# Check the pr-review-state-manager test to see if there are any clues about expected behavior
cat apps/frontend/src/main/__tests__/pr-review-state-manager.test.ts | head -100

Repository: AndyMik90/Auto-Claude

Length of output: 3783


🏁 Script executed:

# Also check how webContents.send payload structure matches what preload expects
rg "webContents.send.*GITHUB_AUTH_CHANGED" apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts -B 10 -A 3

Repository: AndyMik90/Auto-Claude

Length of output: 718


🏁 Script executed:

# Check if there's any documentation or comment about why this pattern was chosen
cat apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts | grep -A 20 "sendAuthChangedToRenderer"

Repository: AndyMik90/Auto-Claude

Length of output: 2177


🏁 Script executed:

# Verify what GitHubAuthChangedPayload is and confirm the types
rg "GitHubAuthChangedPayload" apps/frontend/src --type ts -B 2 -A 5

Repository: AndyMik90/Auto-Claude

Length of output: 1509


ipcMain.emit() listener has incorrect signature — ignores the payload argument.

The listener registered in pr-handlers.ts uses signature () => {} (no parameters), but ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, payload) passes the GitHubAuthChangedPayload object. The payload is silently discarded.

All other ipcMain.on() listeners in the codebase follow the pattern (_, arg1, arg2, ...), consuming the first parameter (even if unused). This listener breaks that convention, making it fragile—if future code tries to use the payload data, it will fail silently.

Pass the payload as a second argument to align with the established pattern, or if the listener only signals state changes without data, use a simple signal mechanism instead:

Proposed fix

Option A: Update listener signature to consume the payload:

-  ipcMain.on(IPC_CHANNELS.GITHUB_AUTH_CHANGED, () => {
+  ipcMain.on(IPC_CHANNELS.GITHUB_AUTH_CHANGED, (_, payload: GitHubAuthChangedPayload) => {
     prReviewStateManager.handleAuthChange();
   });

And update the emit call:

-  ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, payload);
+  ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, {}, payload);

Option B (preferred): Use a dedicated Node EventEmitter for main-process-internal events rather than overloading ipcMain.

🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts` around lines 54
- 55, The ipcMain.emit call is currently discarding the GitHubAuthChangedPayload
because listeners follow the (_, arg1, ...) signature; update the emit to pass
the payload as the second argument
(ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, payload)) and/or update the
listener in pr-handlers.ts to accept the payload parameter (change listener
signature to (_, payload: GitHubAuthChangedPayload) => ...) so the payload isn't
silently ignored; alternatively consider replacing this ipcMain internal signal
with a dedicated Node EventEmitter for main-process-only events (preferred) and
move handlers to subscribe to that emitter instead of ipcMain.

Comment on lines +29 to 30
import { PRReviewStateManager } from "../../pr-review-state-manager";
import { getRunnerEnv } from "./utils/runner-env";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use path alias for PRReviewStateManager import.

🔧 Suggested change
-import { PRReviewStateManager } from "../../pr-review-state-manager";
+import { PRReviewStateManager } from "@/main/pr-review-state-manager";
As per coding guidelines: Frontend code must use path aliases defined in tsconfig.json (`@/*`, `@shared/*`, `@preload/*`, `@features/*`, `@components/*`, `@hooks/*`, `@lib/*`).
📝 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
import { PRReviewStateManager } from "../../pr-review-state-manager";
import { getRunnerEnv } from "./utils/runner-env";
import { PRReviewStateManager } from "@/main/pr-review-state-manager";
import { getRunnerEnv } from "./utils/runner-env";
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts` around lines 29 -
30, Replace the relative import of PRReviewStateManager with the project
path-alias form: find the import statement that currently references
"../../pr-review-state-manager" and change it to use the frontend alias (e.g.
"@/pr-review-state-manager" or the appropriate alias from tsconfig) so
PRReviewStateManager is imported via the configured path alias rather than a
relative path; update any sibling imports in the same file to match the alias
convention if present.

Comment on lines +1 to +3
import { describe, it, expect } from 'vitest';
import { createActor } from 'xstate';
import { prReviewMachine, type PRReviewEvent } from '../pr-review-machine';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use path aliases for shared state-machine imports.
Line 3 should use the configured alias instead of a relative path.

🔧 Suggested change
-import { prReviewMachine, type PRReviewEvent } from '../pr-review-machine';
+import { prReviewMachine, type PRReviewEvent } from '@shared/state-machines/pr-review-machine';
As per coding guidelines: Frontend code must use path aliases defined in tsconfig.json (`@/*`, `@shared/*`, `@preload/*`, `@features/*`, `@components/*`, `@hooks/*`, `@lib/*`).
📝 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
import { describe, it, expect } from 'vitest';
import { createActor } from 'xstate';
import { prReviewMachine, type PRReviewEvent } from '../pr-review-machine';
import { describe, it, expect } from 'vitest';
import { createActor } from 'xstate';
import { prReviewMachine, type PRReviewEvent } from '@shared/state-machines/pr-review-machine';
🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/__tests__/pr-review-machine.test.ts`
around lines 1 - 3, Replace the relative import of the state machine in the test
with the configured path alias: change the import that brings in prReviewMachine
and PRReviewEvent (currently from '../pr-review-machine') to use the tsconfig
alias for shared state-machines (e.g.,
'@shared/state-machines/pr-review-machine'); update the import line in the test
so createActor and the machine types still reference prReviewMachine and
PRReviewEvent but via the alias.

Comment on lines +1 to +2
import { assign, createMachine } from 'xstate';
import type { PRReviewProgress, PRReviewResult } from '../../preload/api/modules/github-api';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use @preload alias for API type import.

🔧 Suggested change
-import type { PRReviewProgress, PRReviewResult } from '../../preload/api/modules/github-api';
+import type { PRReviewProgress, PRReviewResult } from '@preload/api/modules/github-api';
As per coding guidelines: Frontend code must use path aliases defined in tsconfig.json (`@/*`, `@shared/*`, `@preload/*`, `@features/*`, `@components/*`, `@hooks/*`, `@lib/*`).
📝 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
import { assign, createMachine } from 'xstate';
import type { PRReviewProgress, PRReviewResult } from '../../preload/api/modules/github-api';
import { assign, createMachine } from 'xstate';
import type { PRReviewProgress, PRReviewResult } from '@preload/api/modules/github-api';
🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/pr-review-machine.ts` around lines 1
- 2, Replace the relative import of the API types with the tsconfig path alias:
change the import that brings in PRReviewProgress and PRReviewResult from
'../../preload/api/modules/github-api' to use the `@preload` alias (import the
same types from '@preload/api/modules/github-api') in pr-review-machine.ts so
the frontend follows the project's path-alias conventions.

Comment on lines +25 to +27
export const PR_REVIEW_SETTLED_STATES: ReadonlySet<string> = new Set<PRReviewStateName>([
'completed', 'error'
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

PR_REVIEW_SETTLED_STATES type is wider than necessary.

The set is declared as ReadonlySet<string> but constructed from PRReviewStateName values. If the intent is to accept arbitrary strings from XState snapshot values (e.g., snapshot.value typed as string), the widening is intentional and fine. However, if consumers should only pass known state names, narrowing to ReadonlySet<PRReviewStateName> would catch typos at compile time.

🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/pr-review-state-utils.ts` around
lines 25 - 27, The exported PR_REVIEW_SETTLED_STATES is typed as
ReadonlySet<string> but is populated with PRReviewStateName values; narrow its
type to ReadonlySet<PRReviewStateName> to catch typos and enforce known state
names (change the declaration of PR_REVIEW_SETTLED_STATES accordingly), and
update any consumers that currently pass plain string snapshots (e.g.,
snapshot.value) to either cast to PRReviewStateName or validate/convert before
lookup so types remain consistent.

Comment on lines +37 to +51
export function mapPRReviewStateToLegacy(state: string): LegacyPRReviewStatus {
switch (state) {
case 'idle':
return 'idle';
case 'reviewing':
return 'reviewing';
case 'externalReview':
return 'reviewing';
case 'completed':
return 'completed';
case 'error':
return 'error';
default:
return 'idle';
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Silent fallback for unknown states could mask bugs.

The default → 'idle' fallback is reasonable for backward compatibility, but a new state added to the machine without updating this function would silently degrade to 'idle'. Consider adding a console.warn in the default branch during development to surface unmapped states early.

💡 Optional: warn on unknown state
     default:
+      if (process.env.NODE_ENV === 'development') {
+        console.warn(`[mapPRReviewStateToLegacy] Unknown state: "${state}", falling back to 'idle'`);
+      }
       return 'idle';
🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/pr-review-state-utils.ts` around
lines 37 - 51, The function mapPRReviewStateToLegacy currently returns 'idle'
for unknown states which can hide new-state regressions; update the default
branch in mapPRReviewStateToLegacy to log a warning (e.g., using console.warn or
process.env.NODE_ENV check to only warn in development) that includes the
unexpected state value and the target type LegacyPRReviewStatus so maintainers
see unmapped states early; keep the fallback return 'idle' for compatibility but
ensure the warning message identifies the function name and the unmapped state.

@AndyMik90 AndyMik90 self-assigned this Feb 14, 2026
Copy link
Owner Author

@AndyMik90 AndyMik90 left a comment

Choose a reason for hiding this comment

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

🤖 Auto Claude PR Review

Merge Verdict: 🔴 BLOCKED

🔴 Blocked - 4 critical/high/medium issue(s) must be fixed.

Blocked by 1 critical issues

Risk Assessment

Factor Level Notes
Complexity High Based on lines changed
Security Impact None Based on security findings
Scope Coherence Good Based on structural review

🚨 Blocking Issues (Must Fix)

  • Critical: External review polling detects completion but never notifies XState (apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx:434)

Findings Summary

  • Critical: 1 issue(s)
  • High: 1 issue(s)
  • Medium: 2 issue(s)

Generated by Auto Claude PR Review

Findings (4 selected of 4 total)

🟡 [9a2f8c1945a5] [MEDIUM] External review polling is now a no-op but still runs for 30 minutes

📁 apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx:420

The external review polling useEffect (lines 413-448) was gutted: the timeout handler only logs a console.warn (line 424) instead of transitioning the UI out of the reviewing state, and when a completed result IS found (line 434), it only logs to console (line 436) without doing anything. The comment on line 423 claims 'XState now handles external review state - timeout is managed by the state machine', but the XState machine has NO timeout mechanism — there are no after delayed transitions in the externalReview state.

This means: (1) if an external review completes, this polling detects it but does nothing — the user stays stuck in 'reviewing' state forever unless some other IPC event fires, and (2) the 30-minute timeout is now silently broken — after timeout the polling stops but the state machine remains in externalReview indefinitely with no way to exit.

Either the polling should still call the state manager (e.g., via an IPC call to transition the actor), or the machine should implement after delayed transitions for timeout handling.

Suggested fix:

Either: (1) Add an XState `after` delayed transition in the `externalReview` state to handle timeout, or (2) restore the polling's ability to trigger a state change — e.g., call a new IPC endpoint that sends REVIEW_ERROR to the actor on timeout, and REVIEW_COMPLETE when a completed result is detected. Alternatively, remove the polling entirely if it's truly dead code now, rather than leaving misleading comments.

🟠 [f92dc3905d2d] [HIGH] externalReview state is missing CANCEL_REVIEW and CLEAR_REVIEW handlers

📁 apps/frontend/src/shared/state-machines/pr-review-machine.ts:87

The externalReview state only handles REVIEW_COMPLETE and REVIEW_ERROR. It does not handle CANCEL_REVIEW or CLEAR_REVIEW. This means: (1) If a user cancels while in externalReview, handleCancel sends CANCEL_REVIEW which is silently dropped — the state machine remains in externalReview and isReviewing stays true. (2) If handleClearReview is called, it sends CLEAR_REVIEW then stops the actor, but the CLEAR_REVIEW event is dropped (no transition to idle occurs before stop). (3) Auth change via handleAuthChange stops the actor, which works since it doesn't rely on the event. Both the reviewing, completed, and error states handle these events, but externalReview does not, making it an asymmetric gap. Concrete scenario: User starts review → BotDetector returns in_progress → actor enters externalReview → user clicks 'Cancel Review' → handleCancel called → CANCEL_REVIEW dropped → UI still shows reviewing. | The externalReview state only handles REVIEW_COMPLETE and REVIEW_ERROR events (lines 87-97). Unlike the reviewing state, it has no CANCEL_REVIEW transition. If a user tries to cancel while in externalReview state, handleCancel in the state manager calls actor.send({ type: 'CANCEL_REVIEW' }) (line 76 of pr-review-state-manager.ts), but this event will be silently dropped by XState since there's no handler for it in externalReview. The user's cancel request will appear to succeed (the IPC handler returns true and cleans up runningReviews), but the XState actor remains stuck in externalReview and the renderer continues showing 'reviewing' state. Similarly, CLEAR_REVIEW is not handled, so clearing won't work either.

Verified by reading the complete externalReview state definition — only REVIEW_COMPLETE and REVIEW_ERROR are handled.

Suggested fix:

Add `CANCEL_REVIEW` and `CLEAR_REVIEW` handlers to the `externalReview` state, mirroring the `reviewing` state:
```typescript
externalReview: {
  on: {
    REVIEW_COMPLETE: { target: 'completed', actions: 'setResult' },
    REVIEW_ERROR: { target: 'error', actions: 'setError' },
    CANCEL_REVIEW: { target: 'error', actions: 'setCancelledError' },
    CLEAR_REVIEW: { target: 'idle', actions: 'clearContext' },
  },
},

#### 🔴 [641946ce7665] [CRITICAL] External review polling detects completion but never notifies XState
📁 `apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx:434`

The external review completion polling loop detects when a review finishes on disk (line 430: `result.overallStatus !== 'in_progress'`) but the action that previously updated the store (`setPRReviewResult`) was replaced with only a `console.log`. The comment says 'XState now handles review completion via IPC events' but there is NO main-process mechanism that sends a `REVIEW_COMPLETE` event to the actor in `externalReview` state. The old code flow was: PRDetail polls disk → finds completed result → calls `setPRReviewResult` → store updates → UI refreshes. The new code flow is: PRDetail polls disk → finds completed result → logs to console → nothing happens. The XState actor remains stuck in `externalReview` state forever, keeping `isReviewing: true` in the Zustand store, and the UI never shows the review result. This also means the 30-minute timeout (line 422-426) just stops the interval but doesn't transition the state machine out of `externalReview` either — the old code at least set a timeout error result. Concrete scenario: (1) User starts review, (2) BotDetector returns `in_progress`, (3) actor enters `externalReview` state, (4) external review finishes and writes result to disk, (5) PRDetail polling detects it but does nothing, (6) UI shows spinner forever.

**Suggested fix:**

Either: (A) Restore the renderer-side store update by calling an IPC method that forwards the completed result to prReviewStateManager.handleComplete() on the main process, or (B) Add a file watcher / polling mechanism in the main process PRReviewStateManager that detects external review completion and sends REVIEW_COMPLETE to the actor. Option A is simplest — add an IPC handler that the renderer can call when it detects external review completion, which routes through handleComplete. Also add CANCEL_REVIEW and CLEAR_REVIEW handlers to the externalReview state so timeout and cleanup work correctly.


#### 🟡 [f9894e742459] [MEDIUM] sendReviewStateUpdate silently fails — REVIEW_COMPLETE dropped in completed state
📁 `apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts:1388`

The `sendReviewStateUpdate` function (called after posting findings, marking as posted, and deleting reviews at lines 2192, 2231, 2349) reads the updated review result from disk and calls `prReviewStateManager.handleComplete()`. This sends a `REVIEW_COMPLETE` event to the actor. However, at this point the actor is already in the `completed` state (the review finished earlier), and the `completed` state does NOT handle `REVIEW_COMPLETE` — it only handles `START_REVIEW`, `START_FOLLOWUP_REVIEW`, and `CLEAR_REVIEW` (pr-review-machine.ts lines 99-113). The event is silently dropped by XState, so the updated result (with `hasPostedFindings: true`, `postedAt`, or cleared review ID) never reaches the renderer via this path. The UI only updates because `useGitHubPRs` calls `setLoadedReviewResult` separately after the IPC call returns. However, this creates a fragile coupling — if any caller relies solely on `sendReviewStateUpdate` for UI refresh, it will silently fail.

**Suggested fix:**

Either: (A) Add a REVIEW_COMPLETE self-transition handler to the completed state that updates the result context (allowing re-emission of updated result data), or (B) Change sendReviewStateUpdate to directly emit via emitStateToRenderer or use a dedicated UPDATE_RESULT event type that the completed state handles, or (C) If the caller-side setLoadedReviewResult is the intended update path, remove sendReviewStateUpdate calls entirely since they're no-ops now.


---
*This review was generated by Auto Claude.*

…dlers, and result updates

- Add CANCEL_REVIEW and CLEAR_REVIEW handlers to externalReview state so users
  can cancel or clear while an external review is in progress
- Add REVIEW_COMPLETE self-transition in completed state so sendReviewStateUpdate
  can update result context (e.g., after posting findings or marking as posted)
- Add notifyExternalReviewComplete IPC channel so renderer polling can notify the
  main process when an external review finishes on disk or times out, transitioning
  the XState actor to completed/error instead of leaving it stuck in externalReview
- Wire PRDetail.tsx external review polling to use the new IPC notification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx (1)

420-438: ⚠️ Potential issue | 🟡 Minor

Guard IPC notify calls to avoid unhandled rejections.
The timeout branch awaits notifyExternalReviewComplete outside a try/catch; if IPC fails, this can surface as an unhandled rejection. Consider wrapping both notify calls in a helper with error handling.

🔧 Suggested fix
+    const safeNotifyExternalReviewComplete = async (payload: PRReviewResult | null) => {
+      try {
+        await window.electronAPI.github.notifyExternalReviewComplete(projectId, pr.number, payload);
+      } catch (err) {
+        console.warn('[PRDetail] Failed to notify external review completion', err);
+      }
+    };
+
     const pollForCompletion = async () => {
       // Timeout: stop polling after 30 minutes to avoid indefinite polling
       if (Date.now() - pollStart > MAX_POLL_DURATION_MS) {
         console.warn('[PRDetail] External review polling timed out after 30 minutes');
         // Notify main process so the XState actor transitions to error state
-        await window.electronAPI.github.notifyExternalReviewComplete(projectId, pr.number, null);
+        await safeNotifyExternalReviewComplete(null);
         return;
       }
 
       try {
         const result = await window.electronAPI.github.getPRReview(projectId, pr.number);
         if (result && result.overallStatus !== 'in_progress') {
           // Only accept results that were produced AFTER we detected the external review.
           // Otherwise this is a stale result from a previous review still on disk
           // (in-progress results are intentionally NOT saved to disk).
           if (startedAt && result.reviewedAt && new Date(result.reviewedAt) > new Date(startedAt)) {
             // Notify main process so the XState actor transitions to completed state
-            await window.electronAPI.github.notifyExternalReviewComplete(projectId, pr.number, result);
+            await safeNotifyExternalReviewComplete(result);
           }
         }
       } catch {
         // Ignore errors — transient file read failures shouldn't stop polling
       }
     };
🤖 Fix all issues with AI agents
In `@apps/frontend/src/shared/state-machines/pr-review-machine.ts`:
- Around line 182-185: The assignment in setCancelledError currently hardcodes
the user-facing message ('Review cancelled by user') into context.error; replace
that literal with a translation key or constant (e.g.,
ERROR_KEYS.REVIEW_CANCELLED or 'error.reviewCancelled') so the UI can localize
it; update the assign call in setCancelledError to set error to that
key/constant (and ensure any code reading context.error / the renderer maps the
key to a localized string), keeping the same nulling of progress.
- Around line 149-170: Extract the duplicated reset logic used by setReviewStart
and setFollowupReviewStart into a small helper (e.g., resetReviewContext or
buildBaseReviewAssign) that returns the shared assignments: startedAt, progress,
result, error, isExternalReview, and isFollowup (set to false by default) and
reuse it in both assign calls; keep only the differing fields (prNumber,
projectId, isFollowup true for followups, and previousResult for
setFollowupReviewStart) in each action so both setReviewStart and
setFollowupReviewStart call assign with the helper's returned assignments merged
with their specific overrides.
- Around line 62-65: The guard function isNotAlreadyReviewing always returns
false which makes the START_REVIEW transition in the 'reviewing' state dead
code; either remove the START_REVIEW transition from the 'reviewing' state and
delete the unused isNotAlreadyReviewing guard, or if you intended it as a future
placeholder keep the transition and the guard but replace the always-false
implementation with a clear comment explaining it’s a deliberate placeholder for
future re-entry logic (mentioning isNotAlreadyReviewing and START_REVIEW so
reviewers can find them).

Comment on lines +149 to +170
setReviewStart: assign({
prNumber: ({ event }) => (event as { prNumber: number }).prNumber,
projectId: ({ event }) => (event as { projectId: string }).projectId,
startedAt: () => new Date().toISOString(),
isFollowup: () => false,
progress: () => null,
result: () => null,
previousResult: () => null,
error: () => null,
isExternalReview: () => false,
}),
setFollowupReviewStart: assign({
prNumber: ({ event }) => (event as { prNumber: number }).prNumber,
projectId: ({ event }) => (event as { projectId: string }).projectId,
startedAt: () => new Date().toISOString(),
isFollowup: () => true,
progress: () => null,
result: () => null,
previousResult: ({ event }) => (event as { previousResult: PRReviewResult }).previousResult,
error: () => null,
isExternalReview: () => false,
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

setReviewStart and setFollowupReviewStart share ~80% of their logic — consider extracting common reset fields.

Seven of nine property assignments are identical between the two actions. A small helper would reduce duplication and ensure future context fields are reset consistently in both paths.

♻️ Proposed refactor
+const baseReviewStart = (event: { prNumber: number; projectId: string }) => ({
+  prNumber: event.prNumber,
+  projectId: event.projectId,
+  startedAt: new Date().toISOString(),
+  progress: null as PRReviewProgress | null,
+  result: null as PRReviewResult | null,
+  error: null as string | null,
+  isExternalReview: false,
+});
+
 // In the actions config:
-      setReviewStart: assign({
-        prNumber: ({ event }) => (event as { prNumber: number }).prNumber,
-        projectId: ({ event }) => (event as { projectId: string }).projectId,
-        startedAt: () => new Date().toISOString(),
-        isFollowup: () => false,
-        progress: () => null,
-        result: () => null,
-        previousResult: () => null,
-        error: () => null,
-        isExternalReview: () => false,
-      }),
-      setFollowupReviewStart: assign({
-        prNumber: ({ event }) => (event as { prNumber: number }).prNumber,
-        projectId: ({ event }) => (event as { projectId: string }).projectId,
-        startedAt: () => new Date().toISOString(),
-        isFollowup: () => true,
-        progress: () => null,
-        result: () => null,
-        previousResult: ({ event }) => (event as { previousResult: PRReviewResult }).previousResult,
-        error: () => null,
-        isExternalReview: () => false,
-      }),
+      setReviewStart: assign(({ event }) => {
+        const e = event as { prNumber: number; projectId: string };
+        return { ...baseReviewStart(e), isFollowup: false, previousResult: null };
+      }),
+      setFollowupReviewStart: assign(({ event }) => {
+        const e = event as { prNumber: number; projectId: string; previousResult: PRReviewResult };
+        return { ...baseReviewStart(e), isFollowup: true, previousResult: e.previousResult };
+      }),
🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/pr-review-machine.ts` around lines
149 - 170, Extract the duplicated reset logic used by setReviewStart and
setFollowupReviewStart into a small helper (e.g., resetReviewContext or
buildBaseReviewAssign) that returns the shared assignments: startedAt, progress,
result, error, isExternalReview, and isFollowup (set to false by default) and
reuse it in both assign calls; keep only the differing fields (prNumber,
projectId, isFollowup true for followups, and previousResult for
setFollowupReviewStart) in each action so both setReviewStart and
setFollowupReviewStart call assign with the helper's returned assignments merged
with their specific overrides.

Comment on lines +182 to +185
setCancelledError: assign({
error: () => 'Review cancelled by user',
progress: () => null,
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Hardcoded user-facing string 'Review cancelled by user' may need localization.

This string is set into context.error and will likely be rendered in the UI. While the i18n guideline specifically targets JSX/TSX, consider using a translation key or a constant/enum that the renderer can map to a localized string, to avoid a later cleanup pass.

🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/pr-review-machine.ts` around lines
182 - 185, The assignment in setCancelledError currently hardcodes the
user-facing message ('Review cancelled by user') into context.error; replace
that literal with a translation key or constant (e.g.,
ERROR_KEYS.REVIEW_CANCELLED or 'error.reviewCancelled') so the UI can localize
it; update the assign call in setCancelledError to set error to that
key/constant (and ensure any code reading context.error / the renderer maps the
key to a localized string), keeping the same nulling of progress.

Copy link
Owner Author

@AndyMik90 AndyMik90 left a comment

Choose a reason for hiding this comment

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

🤖 Auto Claude PR Review

🟠 Follow-up Review: Needs Revision

🟠 Needs revision - 3 blocking issue(s) require fixes.

Resolution Status

  • Resolved: 4 previous findings addressed
  • Unresolved: 0 previous findings remain
  • 🆕 New Issues: 7 new findings in recent changes

Finding Validation

  • 🔍 Dismissed as False Positives: 4 findings were re-investigated and found to be incorrect
  • Confirmed Valid: 6 findings verified as genuine issues
  • 👤 Needs Human Review: 1 findings require manual verification

🚨 Blocking Issues

  • quality: Unhandled rejection in timeout branch of external review polling
  • quality: handleAuthChange emits prNumber: 0 and projectId: '' for null snapshot
  • quality: [FROM COMMENTS] Unhandled rejection in timeout branch of external review polling (from CodeRabbit)

Verdict

All 20 CI checks passing ✅. All 4 previous blocking findings (1 critical, 1 high, 2 medium) are fully resolved — excellent work introducing a well-designed XState state machine architecture with comprehensive tests. However, 1 confirmed MEDIUM finding remains: the timeout branch of external review polling (PRDetail.tsx:425) awaits notifyExternalReviewComplete outside a try/catch, risking an unhandled promise rejection that could crash the Electron renderer. This is a one-line try/catch fix. A second MEDIUM (NEW-003: prNumber:0 emitted on auth change) is technically valid but heavily mitigated since the auth change handler immediately wipes the store. 4 LOW findings are confirmed valid but non-blocking (dead code guard, ipcMain.emit semantics, benign polling duplicate, premature handleStartReview order). 4 findings dismissed as false positives after validation.

Review Process

Agents invoked: resolution-verifier, new-code-reviewer, comment-analyzer, finding-validator


This is an AI-generated follow-up review using parallel specialist analysis with finding validation.

Findings (7 selected of 7 total)

🟡 [CMT-001] [MEDIUM] Unhandled rejection in timeout branch of external review polling

📁 apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx:425

The timeout branch (lines 422-427) calls await notifyExternalReviewComplete() OUTSIDE the try/catch block starting at line 429. Since pollForCompletion is called fire-and-forget via setInterval, if the IPC call fails this produces an unhandled promise rejection which can crash the Electron renderer. The success-branch call at line 437 IS properly guarded inside try/catch.

Suggested fix:

Wrap the timeout-branch IPC call in try/catch: `try { await window.electronAPI.github.notifyExternalReviewComplete(projectId, pr.number, null); } catch { console.error('[PRDetail] Failed to notify external review timeout'); }`

🟡 [NEW-003] [MEDIUM] handleAuthChange emits prNumber: 0 and projectId: '' for null snapshot

📁 apps/frontend/src/main/pr-review-state-manager.ts:90

When handleAuthChange stops actors and calls emitStateToRenderer(key, null), the payload contains prNumber: 0 and projectId: '' because ctx is null. This is heavily mitigated because the renderer's separate onGitHubAuthChanged listener immediately wipes prReviews to {}, but the garbage data briefly exists in the store.

Suggested fix:

Parse the key to extract real projectId/prNumber for the payload, or remove the emitStateToRenderer calls since the renderer auth handler already wipes everything.

🔵 [NEW-001] [LOW] Dead code: START_REVIEW transition in reviewing state with always-false guard

📁 apps/frontend/src/shared/state-machines/pr-review-machine.ts:62

The isNotAlreadyReviewing guard (line 146) is hardcoded to () => false, making the START_REVIEW transition in the reviewing state (lines 62-65) unreachable dead code. Removing the transition entirely would produce identical behavior.

Suggested fix:

Remove the START_REVIEW transition (lines 62-65) from the reviewing state and the isNotAlreadyReviewing guard (line 146).

🔵 [NEW-005] [LOW] ipcMain.emit uses EventEmitter semantics, not IPC conventions

📁 apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts:55

ipcMain.emit(channel, payload) passes payload directly as first argument (EventEmitter pattern), but ipcMain.on listeners normally receive (event, ...args) with IPC semantics. Works because the listener ignores all arguments, but could confuse future maintainers.

Suggested fix:

Add a code comment explaining that this uses EventEmitter.emit (not IPC send) and the listener intentionally ignores args.

🔵 [NEW-010] [LOW] External review polling may send 1-2 duplicate notifications before React cleanup

📁 apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx:437

After notifyExternalReviewComplete succeeds, the setInterval continues until React re-renders and the cleanup function fires. This sends benign duplicate notifications (completed state handles REVIEW_COMPLETE as self-transition, dedup prevents redundant renderer emissions).

Suggested fix:

Add a local `let completed = false` ref to short-circuit subsequent polls after the first successful notification.

🔵 [NEW-011] [LOW] handleStartReview called before duplicate-review check

📁 apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts:1875

prReviewStateManager.handleStartReview is called at line 1875 before runningReviews.has(reviewKey) check at line 1884. The state machine's isNotAlreadyReviewing guard prevents actual harm, but the ordering is inverted from design intent.

Suggested fix:

Move the runningReviews.has check before handleStartReview, or combine them.

🟡 [CMT-001] [MEDIUM] [FROM COMMENTS] Unhandled rejection in timeout branch of external review polling (from CodeRabbit)

📁 apps/frontend/src/renderer/components/github-prs/components/PRDetail.tsx:425

The timeout branch calls await notifyExternalReviewComplete() outside the try/catch. If the IPC call fails, it produces an unhandled promise rejection. The success-branch call IS inside try/catch. CodeRabbit correctly identified this asymmetry.

Suggested fix:

Wrap the timeout-branch IPC call in try/catch.

This review was generated by Auto Claude.

AndyMik90 and others added 2 commits February 14, 2026 11:23
…yload, polling guard

- Wrap timeout-branch IPC call in try/catch to prevent unhandled promise rejection
  in the Electron renderer when notifyExternalReviewComplete fails
- Add `notified` flag to short-circuit duplicate polling notifications before React
  cleanup fires
- Use last snapshot context in handleAuthChange so emitted cleared-state payload
  contains real projectId/prNumber instead of zeros
- Move handleStartReview after duplicate-review check so state machine isn't
  transitioned for already-running reviews

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

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

Caution

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

⚠️ Outside diff range comments (1)
apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts (1)

2895-2923: ⚠️ Potential issue | 🔴 Critical

Bug: handleStartFollowupReview is called before the duplicate-review check, risking a stuck reviewing state.

Unlike the regular review handler (lines 1881–1893) where handleStartReview correctly runs after the duplicate check, the follow-up handler calls handleStartFollowupReview at line 2901 before checking runningReviews.has(reviewKey) at line 2920. If a follow-up review is already running, the function returns early at line 2922 but the actor has already transitioned to reviewing — it will remain stuck there indefinitely.

Move handleStartFollowupReview after the duplicate check, mirroring the fix applied to the regular review path.

🐛 Proposed fix
-      // Get previous result for followup context
-      const previousResult = await withProjectOrNull(projectId, async (project) => {
-        return getReviewResult(project, prNumber) ?? undefined;
-      }) ?? undefined;
-
-      // Notify state manager that followup review is starting
-      prReviewStateManager.handleStartFollowupReview(projectId, prNumber, previousResult);
-
       try {
         await withProjectOrNull(projectId, async (project) => {
           const sendProgress = (progress: PRReviewProgress): void => {
             prReviewStateManager.handleProgress(projectId, prNumber, progress);
           };
 
           // Comprehensive validation of GitHub module
           const validation = await validateGitHubModule(project);
           if (!validation.valid) {
             prReviewStateManager.handleError(projectId, prNumber, validation.error || "GitHub module validation failed");
             return;
           }
 
           const backendPath = validation.backendPath!;
           const reviewKey = getReviewKey(projectId, prNumber);
 
           // Check if already running
           if (runningReviews.has(reviewKey)) {
             debugLog("Follow-up review already running", { reviewKey });
             return;
           }
 
+          // Get previous result for followup context (after duplicate check)
+          const previousResult = getReviewResult(project, prNumber) ?? undefined;
+
+          // Notify state manager that followup review is starting
+          prReviewStateManager.handleStartFollowupReview(projectId, prNumber, previousResult);
+
           // Register as running BEFORE CI wait to prevent race conditions

Copy link
Owner Author

@AndyMik90 AndyMik90 left a comment

Choose a reason for hiding this comment

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

🤖 Auto Claude PR Review

🟠 Follow-up Review: Needs Revision

🟠 Needs revision - 2 unresolved finding(s) from previous review.

Resolution Status

  • Resolved: 5 previous findings addressed
  • Unresolved: 2 previous findings remain
  • 🆕 New Issues: 3 new findings in recent changes

Finding Validation

  • 🔍 Dismissed as False Positives: 2 findings were re-investigated and found to be incorrect
  • Confirmed Valid: 3 findings verified as genuine issues
  • 👤 Needs Human Review: 1 findings require manual verification

🚨 Blocking Issues

  • quality: handleStartFollowupReview called before duplicate-review check causes stuck reviewing state
  • quality: handleClearReview silently drops CLEAR_REVIEW when actor is in reviewing state
  • quality: [FROM COMMENTS] [FROM COMMENTS] handleStartFollowupReview ordering bug (from CodeRabbit)

Verdict

All 20 CI checks passing ✅. Excellent progress: 5 of 7 previous findings resolved (including all 3 blocking medium-severity findings). However, 1 new HIGH severity issue was identified and confirmed by both the new-code-reviewer and comment-analyzer (CodeRabbit also flagged this): the follow-up review handler calls handleStartFollowupReview at line 2901 BEFORE the runningReviews.has() duplicate check at line 2920, which can leave the XState actor permanently stuck in 'reviewing' state if a follow-up review is triggered while one is already running. The regular review handler correctly does this in the right order (lines 1880-1893). Fix: move handleStartFollowupReview after the duplicate check, mirroring the regular handler pattern. 2 remaining LOW findings (dead code guard, ipcMain.emit pattern) are non-blocking.

Review Process

Agents invoked: resolution-verifier, new-code-reviewer, comment-analyzer, finding-validator


This is an AI-generated follow-up review using parallel specialist analysis with finding validation.

Findings (5 selected of 5 total)

🔵 [NEW-001] [LOW] [UNRESOLVED] Dead code: START_REVIEW transition in reviewing state with always-false guard

📁 apps/frontend/src/shared/state-machines/pr-review-machine.ts:62

The isNotAlreadyReviewing guard (line 146) is hardcoded to () => false, making the START_REVIEW transition in the reviewing state (lines 62-65) unreachable dead code. Removing the transition entirely would produce identical behavior.

Resolution note: The isNotAlreadyReviewing guard at line 146 of pr-review-machine.ts is still hardcoded to () => false, making the START_REVIEW transition in the reviewing state (lines 62-65) permanently unreachable dead code.

Suggested fix:

Remove the START_REVIEW transition (lines 62-65) from the reviewing state and the isNotAlreadyReviewing guard (line 146).

🔵 [NEW-005] [LOW] [UNRESOLVED] ipcMain.emit uses EventEmitter semantics, not IPC conventions

📁 apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts:55

ipcMain.emit(channel, payload) passes payload directly as first argument (EventEmitter pattern), but ipcMain.on listeners normally receive (event, ...args) with IPC semantics. Works because the listener ignores all arguments, but could confuse future maintainers.

Resolution note: ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, payload) at line 55 of oauth-handlers.ts still uses EventEmitter semantics. The listener at pr-handlers.ts:1665 ignores all args (() => {}), so it works functionally, but the pattern remains technically incorrect.

Suggested fix:

Add a code comment explaining that this uses EventEmitter.emit (not IPC send) and the listener intentionally ignores args.

🟠 [NEW-FOLLOWUP-ORDER] [HIGH] handleStartFollowupReview called before duplicate-review check causes stuck reviewing state

📁 apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts:2901

In the follow-up review handler, prReviewStateManager.handleStartFollowupReview() at line 2901 transitions the XState actor to 'reviewing' state BEFORE runningReviews.has(reviewKey) is checked at line 2920. If a follow-up review is already running, the handler returns early at line 2922 without sending any error/cancel event, leaving the actor permanently stuck in 'reviewing' state with no subprocess running. The regular review handler (lines 1881-1893) correctly checks duplicates FIRST, then calls handleStartReview. This was also flagged by CodeRabbit as a critical issue.

Suggested fix:

Move the handleStartFollowupReview call from line 2901 to inside the withProjectOrNull callback, after the runningReviews.has() check, matching the pattern used in the regular review handler. The fix also requires computing previousResult inside the withProjectOrNull callback or passing it through.

🟡 [NEW-CLEAR-REVIEW] [MEDIUM] handleClearReview silently drops CLEAR_REVIEW when actor is in reviewing state

📁 apps/frontend/src/main/pr-review-state-manager.ts:79

The XState machine's reviewing state does not handle CLEAR_REVIEW events, so handleClearReview silently drops the event, stops the actor, and does not emit idle state to renderer (unlike handleAuthChange which calls emitClearedState). Currently not reachable in production (only called from tests), but represents an incomplete implementation.

Suggested fix:

Either add CLEAR_REVIEW handling to the reviewing state in the machine definition, or capture the snapshot before stopping and call emitClearedState in handleClearReview (matching the handleAuthChange pattern).

🟠 [CMT-FOLLOWUP-ORDER] [HIGH] [FROM COMMENTS] [FROM COMMENTS] handleStartFollowupReview ordering bug (from CodeRabbit)

📁 apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts:2901

CodeRabbit flagged as Critical: handleStartFollowupReview is called at line 2901 before the duplicate-review check at line 2920, risking a stuck reviewing state. This matches NEW-FOLLOWUP-ORDER finding — confirmed valid by finding-validator. The regular review handler correctly does the check first.

Suggested fix:

Move handleStartFollowupReview after the runningReviews.has() check, mirroring the regular review handler pattern at lines 1881-1893.

This review was generated by Auto Claude.

…ght builds

- Move handleStartFollowupReview after runningReviews.has() duplicate check to
  prevent XState actor getting stuck in reviewing state when follow-up is already
  running (mirroring the regular review handler pattern)
- Add CLEAR_REVIEW handler to reviewing state so handleClearReview works correctly
  when an active review is cleared
- Remove dead-code START_REVIEW transition and always-false isNotAlreadyReviewing
  guard from reviewing state — duplicate prevention is handled at the IPC layer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts (1)

1374-1397: ⚠️ Potential issue | 🟠 Major

buildContextKey in PRReviewStateManager omits critical result fields, breaking UI refresh after post/mark-posted/delete operations.

The deduplication mechanism only includes result.overallStatus in the key, but operations like marking findings as posted or deleting reviews only change hasPostedFindings, postedFindingIds, postedAt, and reviewId — leaving overallStatus unchanged.

When sendReviewStateUpdate calls handleComplete after these operations:

  1. The state machine's completed state correctly accepts REVIEW_COMPLETE and updates context.result
  2. buildContextKey generates an identical key (same overallStatus)
  3. Deduplication comparison fails: currentKey === lastStateByPR.get(key) → emission is skipped
  4. Renderer never receives the UI update

The deduplication key must include all mutable result fields: expand the resultKey in buildContextKey to cover hasPostedFindings, postedFindingIds, postedAt, and reviewId.

🤖 Fix all issues with AI agents
In `@apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts`:
- Line 2192: The deduplication key used by the PR review state manager is too
narrow (it only uses result.overallStatus), so post-completion mutations (like
posting findings) are not emitted to the renderer; update the buildContextKey
used by prReviewStateManager's subscription/dedup logic to include additional
identifying fields from the result (for example result?.reviewId,
result?.hasPostedFindings and/or a serialized result?.postedFindingIds) so that
sendReviewStateUpdate and post-posting paths (the code around
sendReviewStateUpdate and the handlers that reload result from disk) produce a
different key and trigger an emit; alternatively implement a separate
notification path for post-completion changes if you prefer not to expand the
dedupe key.

In `@apps/frontend/src/shared/state-machines/__tests__/pr-review-machine.test.ts`:
- Around line 306-328: The test suite contains duplicated test cases for
rejecting SET_PROGRESS in terminal states; remove the redundant tests in the
"stale events" describe block that repeat the same scenarios already present in
the "guard" block — specifically the tests named 'should reject SET_PROGRESS in
completed state' and 'should reject SET_PROGRESS in error state' which use
runEvents with events { type: 'START_REVIEW', ... }, { type: 'REVIEW_COMPLETE' /
'REVIEW_ERROR' }, { type: 'SET_PROGRESS', ... } and assertions against
snapshot.value and snapshot.context.progress; keep one copy (prefer the original
in the "guard" block) and delete the duplicate functions to avoid repetition.
- Around line 171-194: Add tests to cover CLEAR_REVIEW when the machine is in
the reviewing and externalReview states: use runEvents to START_REVIEW
(prNumber/projectId) then dispatch CLEAR_REVIEW and assert the state becomes
'idle' and that context fields are cleared (for reviewing assert prNumber and
startedAt are null; for externalReview assert prNumber and any
externalReview-specific context fields are null). Reference the existing test
patterns for 'clear review' and symbols START_REVIEW, CLEAR_REVIEW, reviewing,
externalReview, runEvents, and snapshot.context to mirror the other two tests
(completed/error) so regressions are caught.

In `@apps/frontend/src/shared/state-machines/pr-review-machine.ts`:
- Around line 146-177: The actions setReviewStart, setFollowupReviewStart,
setProgress, setResult and setError use unsafe (event as { ... }) casts which
lose compile-time checking; replace those casts by using type-safe inline assign
actions that narrow the event type (or use a narrowEvent<T> helper that returns
Extract<PRReviewEvent,{type:T}>) so each action extracts fields from the
properly narrowed event (e.g., narrowEvent(event,'START_REVIEW') inside
setReviewStart) instead of raw casts; update each action to return the same
state fields but read values from the narrowed event to restore compile-time
safety tied to PRReviewEvent.


// Send state update event to refresh UI immediately (non-blocking)
sendReviewStateUpdate(project, prNumber, projectId, getMainWindow, "after posting");
sendReviewStateUpdate(project, prNumber, projectId, prReviewStateManager, "after posting");
Copy link
Contributor

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
# Find and examine the PR review state machine
find . -name "*pr-review*machine*" -type f | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 205


🏁 Script executed:

#!/bin/bash
# Look for the state machine definition
fd "pr-review-machine" --type f

Repository: AndyMik90/Auto-Claude

Length of output: 201


🏁 Script executed:

#!/bin/bash
# Check the structure of ipc-handlers directory
ls -la apps/frontend/src/main/ipc-handlers/github/ 2>/dev/null || find apps -path "*ipc-handlers*" -type d

Repository: AndyMik90/Auto-Claude

Length of output: 1328


🏁 Script executed:

#!/bin/bash
# Read the PR review state machine
wc -l ./apps/frontend/src/shared/state-machines/pr-review-machine.ts

Repository: AndyMik90/Auto-Claude

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Get the completed state definition
cat -n ./apps/frontend/src/shared/state-machines/pr-review-machine.ts | grep -A 50 "completed:"

Repository: AndyMik90/Auto-Claude

Length of output: 1793


🏁 Script executed:

#!/bin/bash
# Also check the full states object structure to understand event handling
cat -n ./apps/frontend/src/shared/state-machines/pr-review-machine.ts | head -150

Repository: AndyMik90/Auto-Claude

Length of output: 5089


🏁 Script executed:

#!/bin/bash
# Check the setResult action definition
cat -n ./apps/frontend/src/shared/state-machines/pr-review-machine.ts | tail -50

Repository: AndyMik90/Auto-Claude

Length of output: 2088


🏁 Script executed:

#!/bin/bash
# Get the full actions section
sed -n '144,189p' ./apps/frontend/src/shared/state-machines/pr-review-machine.ts

Repository: AndyMik90/Auto-Claude

Length of output: 1706


🏁 Script executed:

#!/bin/bash
# Now check how handleComplete uses sendReviewStateUpdate in pr-handlers.ts
grep -n "handleComplete\|sendReviewStateUpdate" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 761


🏁 Script executed:

#!/bin/bash
# Find the handleComplete function definition
grep -n -A 15 "const handleComplete" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Find the prReviewStateManager and its handleComplete method
rg -n "handleComplete" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts -B 2 -A 8 | head -60

Repository: AndyMik90/Auto-Claude

Length of output: 2237


🏁 Script executed:

#!/bin/bash
# Check the sendReviewStateUpdate function definition
sed -n '1374,1395p' ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 861


🏁 Script executed:

#!/antml:function
# Check what prReviewStateManager is and how the complete flow works
rg -n "prReviewStateManager\s*=" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts | head -5

Repository: AndyMik90/Auto-Claude

Length of output: 141


🏁 Script executed:

#!/bin/bash
# Find the PRReviewStateManager class/object definition
fd "review.*manager\|pr.*manager" --type f -g "*.ts" -E "*test*" | xargs grep -l "handleComplete" 2>/dev/null

Repository: AndyMik90/Auto-Claude

Length of output: 158


🏁 Script executed:

#!/bin/bash
# Find PRReviewStateManager class definition
rg -n "class PRReviewStateManager" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts -A 50 | head -100

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check the entire PRReviewStateManager class
rg -n "class PRReviewStateManager" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts -A 100

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search for PRReviewStateManager definition more broadly
grep -n "PRReviewStateManager" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts | head -10

Repository: AndyMik90/Auto-Claude

Length of output: 315


🏁 Script executed:

#!/bin/bash
# Check if it's imported from elsewhere
grep -n "import.*PRReviewStateManager\|export.*PRReviewStateManager" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Look for the class definition in the entire codebase
fd "\.ts$" -type f ./apps/frontend/src/main | xargs grep -l "class PRReviewStateManager" 2>/dev/null

Repository: AndyMik90/Auto-Claude

Length of output: 237


🏁 Script executed:

#!/bin/bash
# Fix the fd command to find files
fd "pr-review-state-manager" --type f ./apps/frontend/src/main

Repository: AndyMik90/Auto-Claude

Length of output: 183


🏁 Script executed:

#!/antml:function
# Check the pr-review-state-manager file
cat ./apps/frontend/src/main/pr-review-state-manager.ts

Repository: AndyMik90/Auto-Claude

Length of output: 7216


🏁 Script executed:

#!/bin/bash
# Check the PRReviewResult type definition
rg -n "type PRReviewResult\|interface PRReviewResult" ./apps/frontend/src -A 15

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

#!/antml:function
# Check the deduplication key logic more carefully in the context of result changes
sed -n '13,23p' ./apps/frontend/src/main/pr-review-state-manager.ts

Repository: AndyMik90/Auto-Claude

Length of output: 595


🏁 Script executed:

#!/antml:function
# Check what happens after setResult is called - does it trigger a new emit?
sed -n '142,152p' ./apps/frontend/src/main/pr-review-state-manager.ts

Repository: AndyMik90/Auto-Claude

Length of output: 437


🏁 Script executed:

#!/antml:function
# Search for PRReviewResult type to understand what fields it has
find ./apps/frontend/src -name "*.ts" -type f | xargs grep -l "type PRReviewResult\|interface PRReviewResult" | head -5

Repository: AndyMik90/Auto-Claude

Length of output: 279


🏁 Script executed:

#!/antml:function
# Find the type definition in the API or types
rg "type PRReviewResult|interface PRReviewResult" ./apps/frontend/src -A 20

Repository: AndyMik90/Auto-Claude

Length of output: 6762


🏁 Script executed:

#!/antml:function
# Check the preload/api/modules/github-api
cat ./apps/frontend/src/preload/api/modules/github-api.ts | head -100

Repository: AndyMik90/Auto-Claude

Length of output: 2318


🏁 Script executed:

#!/antml:function
# Now check the complete getOrCreateActor flow again
sed -n '127,150p' ./apps/frontend/src/main/pr-review-state-manager.ts

Repository: AndyMik90/Auto-Claude

Length of output: 792


🏁 Script executed:

#!/antml:function
# Double-check the exact deduplication logic and context building
sed -n '13,40p' ./apps/frontend/src/main/pr-review-state-manager.ts

Repository: AndyMik90/Auto-Claude

Length of output: 1223


🏁 Script executed:

#!/antml:function
# Check if there are any other fields in PRReviewResult that change after posting
grep -n "hasPostedFindings\|reviewId\|postedFindingIds" ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts | head -30

Repository: AndyMik90/Auto-Claude

Length of output: 937


🏁 Script executed:

#!/antml:function
# Find where these fields are set in the result after posting (around lines 2192, 2231, 2349)
sed -n '2180,2200p' ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 965


🏁 Script executed:

#!/antml:function
# Check the getReviewResult function
sed -n '1410,1430p' ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 719


🏁 Script executed:

#!/antml:function
# Check what the getReviewResult function returns and how it builds the result
sed -n '1322,1375p' ./apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 2009


🏁 Script executed:

#!/antml:function
# Verify the exact subscription check logic
sed -n '127,145p' ./apps/frontend/src/main/pr-review-state-manager.ts

Repository: AndyMik90/Auto-Claude

Length of output: 653


Post-completion state updates won't reach the renderer due to insufficient deduplication key.

The completed state correctly handles REVIEW_COMPLETE with a setResult action (line 117-119 of pr-review-machine.ts), updating the context. However, the PR review state manager's subscription deduplication logic (line 13-23) only tracks result.overallStatus in the context key, not hasPostedFindings, reviewId, or postedFindingIds. When findings are posted at lines 2192, 2231, or 2349, the result object is reloaded from disk with these fields populated, but because overallStatus typically remains unchanged (e.g., 'approve'), the deduplication check at line 134 prevents the emit to the renderer, leaving the UI stale.

Include additional fields in buildContextKey (e.g., result?.reviewId, result?.hasPostedFindings) to ensure post-completion mutations are detected and emitted, or use a separate mechanism for post-completion state notifications.

🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts` at line 2192, The
deduplication key used by the PR review state manager is too narrow (it only
uses result.overallStatus), so post-completion mutations (like posting findings)
are not emitted to the renderer; update the buildContextKey used by
prReviewStateManager's subscription/dedup logic to include additional
identifying fields from the result (for example result?.reviewId,
result?.hasPostedFindings and/or a serialized result?.postedFindingIds) so that
sendReviewStateUpdate and post-posting paths (the code around
sendReviewStateUpdate and the handlers that reload result from disk) produce a
different key and trigger an emit; alternatively implement a separate
notification path for post-completion changes if you prefer not to expand the
dedupe key.

Comment on lines 171 to 194
describe('clear review', () => {
it('should clear from completed to idle', () => {
const snapshot = runEvents([
{ type: 'START_REVIEW', prNumber: 42, projectId: 'proj-1' },
{ type: 'REVIEW_COMPLETE', result: mockResult },
{ type: 'CLEAR_REVIEW' },
]);

expect(snapshot.value).toBe('idle');
expect(snapshot.context.prNumber).toBeNull();
expect(snapshot.context.result).toBeNull();
});

it('should clear from error to idle', () => {
const snapshot = runEvents([
{ type: 'START_REVIEW', prNumber: 42, projectId: 'proj-1' },
{ type: 'REVIEW_ERROR', error: 'Failed' },
{ type: 'CLEAR_REVIEW' },
]);

expect(snapshot.value).toBe('idle');
expect(snapshot.context.error).toBeNull();
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing test coverage for CLEAR_REVIEW from the reviewing state.

The latest commit specifically added CLEAR_REVIEW handling in the reviewing state to fix a bug (clearing an active review). Tests only cover CLEAR_REVIEW from completed and error states. The new transition should be tested to prevent regressions on this fix.

🧪 Suggested additional test
it('should clear from reviewing to idle', () => {
  const snapshot = runEvents([
    { type: 'START_REVIEW', prNumber: 42, projectId: 'proj-1' },
    { type: 'CLEAR_REVIEW' },
  ]);

  expect(snapshot.value).toBe('idle');
  expect(snapshot.context.prNumber).toBeNull();
  expect(snapshot.context.startedAt).toBeNull();
});

Similarly, CLEAR_REVIEW from the externalReview state is also untested.

🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/__tests__/pr-review-machine.test.ts`
around lines 171 - 194, Add tests to cover CLEAR_REVIEW when the machine is in
the reviewing and externalReview states: use runEvents to START_REVIEW
(prNumber/projectId) then dispatch CLEAR_REVIEW and assert the state becomes
'idle' and that context fields are cleared (for reviewing assert prNumber and
startedAt are null; for externalReview assert prNumber and any
externalReview-specific context fields are null). Reference the existing test
patterns for 'clear review' and symbols START_REVIEW, CLEAR_REVIEW, reviewing,
externalReview, runEvents, and snapshot.context to mirror the other two tests
(completed/error) so regressions are caught.

Comment on lines +306 to +328
describe('stale events', () => {
it('should reject SET_PROGRESS in completed state', () => {
const snapshot = runEvents([
{ type: 'START_REVIEW', prNumber: 42, projectId: 'proj-1' },
{ type: 'REVIEW_COMPLETE', result: mockResult },
{ type: 'SET_PROGRESS', progress: mockProgress },
]);

expect(snapshot.value).toBe('completed');
expect(snapshot.context.progress).toBeNull();
});

it('should reject SET_PROGRESS in error state', () => {
const snapshot = runEvents([
{ type: 'START_REVIEW', prNumber: 42, projectId: 'proj-1' },
{ type: 'REVIEW_ERROR', error: 'Failed' },
{ type: 'SET_PROGRESS', progress: mockProgress },
]);

expect(snapshot.value).toBe('error');
expect(snapshot.context.progress).toBeNull();
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Duplicate test scenarios with the "guard" describe block.

The two tests here ("reject SET_PROGRESS in completed state" and "reject SET_PROGRESS in error state") are exact duplicates of the tests at lines 218–227 and 229–238 respectively — same event sequences, same assertions. Remove one set to avoid maintenance overhead.

🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/__tests__/pr-review-machine.test.ts`
around lines 306 - 328, The test suite contains duplicated test cases for
rejecting SET_PROGRESS in terminal states; remove the redundant tests in the
"stale events" describe block that repeat the same scenarios already present in
the "guard" block — specifically the tests named 'should reject SET_PROGRESS in
completed state' and 'should reject SET_PROGRESS in error state' which use
runEvents with events { type: 'START_REVIEW', ... }, { type: 'REVIEW_COMPLETE' /
'REVIEW_ERROR' }, { type: 'SET_PROGRESS', ... } and assertions against
snapshot.value and snapshot.context.progress; keep one copy (prefer the original
in the "guard" block) and delete the duplicate functions to avoid repetition.

Comment on lines +146 to +177
setReviewStart: assign({
prNumber: ({ event }) => (event as { prNumber: number }).prNumber,
projectId: ({ event }) => (event as { projectId: string }).projectId,
startedAt: () => new Date().toISOString(),
isFollowup: () => false,
progress: () => null,
result: () => null,
previousResult: () => null,
error: () => null,
isExternalReview: () => false,
}),
setFollowupReviewStart: assign({
prNumber: ({ event }) => (event as { prNumber: number }).prNumber,
projectId: ({ event }) => (event as { projectId: string }).projectId,
startedAt: () => new Date().toISOString(),
isFollowup: () => true,
progress: () => null,
result: () => null,
previousResult: ({ event }) => (event as { previousResult: PRReviewResult }).previousResult,
error: () => null,
isExternalReview: () => false,
}),
setProgress: assign({
progress: ({ event }) => (event as { progress: PRReviewProgress }).progress,
}),
setResult: assign({
result: ({ event }) => (event as { result: PRReviewResult }).result,
progress: () => null,
}),
setError: assign({
error: ({ event }) => (event as { error: string }).error,
progress: () => null,
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Unsafe as casts on event payloads lose compile-time safety.

Every action manually casts event to extract fields (e.g., (event as { prNumber: number }).prNumber). If PRReviewEvent is later modified — say a field is renamed or its type changes — these casts will silently produce undefined at runtime instead of failing at compile time.

XState v5 supports inline action implementations that preserve event narrowing. You could define a helper to narrow events by type, or use inline actions where the event type is automatically narrowed by the transition that triggers them:

♻️ Sketch: type-safe narrowing helper
// Helper for narrowing event types used in assign
function narrowEvent<T extends PRReviewEvent['type']>(
  event: PRReviewEvent,
  _type: T
): Extract<PRReviewEvent, { type: T }> {
  return event as Extract<PRReviewEvent, { type: T }>;
}

// Usage in an action:
setReviewStart: assign(({ event }) => {
  const e = narrowEvent(event, 'START_REVIEW');
  return {
    prNumber: e.prNumber,
    projectId: e.projectId,
    startedAt: new Date().toISOString(),
    isFollowup: false,
    progress: null,
    result: null,
    previousResult: null,
    error: null,
    isExternalReview: false,
  };
}),

This still uses a cast internally, but the Extract utility type ties the narrowed shape to PRReviewEvent, so any rename/type change in the union surfaces a compile error at the helper call site.

🤖 Prompt for AI Agents
In `@apps/frontend/src/shared/state-machines/pr-review-machine.ts` around lines
146 - 177, The actions setReviewStart, setFollowupReviewStart, setProgress,
setResult and setError use unsafe (event as { ... }) casts which lose
compile-time checking; replace those casts by using type-safe inline assign
actions that narrow the event type (or use a narrowEvent<T> helper that returns
Extract<PRReviewEvent,{type:T}>) so each action extracts fields from the
properly narrowed event (e.g., narrowEvent(event,'START_REVIEW') inside
setReviewStart) instead of raw casts; update each action to return the same
state fields but read values from the narrowed event to restore compile-time
safety tied to PRReviewEvent.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts`:
- Around line 2498-2511: The IPC handler for
IPC_CHANNELS.GITHUB_PR_NOTIFY_EXTERNAL_REVIEW_COMPLETE forwards an unvalidated
renderer payload to prReviewStateManager.handleComplete; add a lightweight guard
that checks result is not null, result.prNumber === prNumber, typeof
result.success === 'boolean' and that result.overallStatus is one of the allowed
enum values (or otherwise matches expected strings), log a debug/error via
debugLog/processLogger when validation fails, and call
prReviewStateManager.handleError(projectId, prNumber, "<validation failure
message>") instead of handleComplete for malformed payloads to avoid corrupting
the state machine.
- Around line 2902-2908: The handler silently returns when a duplicate follow-up
review is detected (check uses getReviewKey and runningReviews), so update the
duplicate-check branch to notify the renderer exactly like the initial review
handler does: before returning, call the same progress/IPC notification used in
the initial review flow (the "Reconnecting to ongoing review..." progress
message) and keep the debugLog("Follow-up review already running", { reviewKey
}) call; ensure the notification uses the same channel/format the renderer
expects so the user sees the reconnecting message when
runningReviews.has(reviewKey) is true.

Comment on lines +2498 to +2511
// Notify main process about external review completion or timeout
// Called by renderer when its polling detects an external review has finished on disk
ipcMain.handle(
IPC_CHANNELS.GITHUB_PR_NOTIFY_EXTERNAL_REVIEW_COMPLETE,
async (_, projectId: string, prNumber: number, result: PRReviewResult | null): Promise<void> => {
debugLog("notifyExternalReviewComplete handler called", { projectId, prNumber, hasResult: !!result });
if (result) {
prReviewStateManager.handleComplete(projectId, prNumber, result);
} else {
// Timeout — no result found within polling window
prReviewStateManager.handleError(projectId, prNumber, "External review timed out after 30 minutes");
}
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider validating the result payload from the renderer before forwarding to the state manager.

The result: PRReviewResult | null parameter is received directly from the renderer via IPC and passed to handleComplete without shape validation. A malformed payload (e.g., missing prNumber, wrong overallStatus enum value) could put the state machine into an inconsistent state.

Even a lightweight guard (e.g., checking result.prNumber === prNumber and typeof result.success === 'boolean') would add defence in depth.

🛡️ Proposed validation
     async (_, projectId: string, prNumber: number, result: PRReviewResult | null): Promise<void> => {
       debugLog("notifyExternalReviewComplete handler called", { projectId, prNumber, hasResult: !!result });
       if (result) {
+        // Basic sanity check — renderer-supplied data is untrusted
+        if (result.prNumber !== prNumber || typeof result.success !== "boolean") {
+          debugLog("Invalid external review result payload, ignoring", { projectId, prNumber });
+          return;
+        }
         prReviewStateManager.handleComplete(projectId, prNumber, result);
       } else {
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts` around lines 2498
- 2511, The IPC handler for
IPC_CHANNELS.GITHUB_PR_NOTIFY_EXTERNAL_REVIEW_COMPLETE forwards an unvalidated
renderer payload to prReviewStateManager.handleComplete; add a lightweight guard
that checks result is not null, result.prNumber === prNumber, typeof
result.success === 'boolean' and that result.overallStatus is one of the allowed
enum values (or otherwise matches expected strings), log a debug/error via
debugLog/processLogger when validation fails, and call
prReviewStateManager.handleError(projectId, prNumber, "<validation failure
message>") instead of handleComplete for malformed payloads to avoid corrupting
the state machine.

Comment on lines 2902 to 2908
const reviewKey = getReviewKey(projectId, prNumber);

// Check if already running — before notifying state manager to avoid stuck reviewing state
if (runningReviews.has(reviewKey)) {
debugLog("Follow-up review already running", { reviewKey });
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent return on duplicate follow-up review gives no user feedback.

When a follow-up review is already running, this handler returns without notifying the renderer — unlike the initial review handler (lines 1882-1891) which sends a "Reconnecting to ongoing review..." progress message. The user gets no indication that their action was acknowledged.

💡 Proposed fix — mirror the initial review handler behaviour
          if (runningReviews.has(reviewKey)) {
            debugLog("Follow-up review already running", { reviewKey });
+           sendProgress({
+             phase: "analyzing",
+             prNumber,
+             progress: 50,
+             message: "Follow-up review is already in progress. Reconnecting to ongoing review...",
+           });
            return;
          }
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts` around lines 2902
- 2908, The handler silently returns when a duplicate follow-up review is
detected (check uses getReviewKey and runningReviews), so update the
duplicate-check branch to notify the renderer exactly like the initial review
handler does: before returning, call the same progress/IPC notification used in
the initial review flow (the "Reconnecting to ongoing review..." progress
message) and keep the debugLog("Follow-up review already running", { reviewKey
}) call; ensure the notification uses the same channel/format the renderer
expects so the user sees the reconnecting message when
runningReviews.has(reviewKey) is true.

Comment on lines 138 to 141
projectId,
isReviewing: false,
startedAt: existing?.startedAt ?? null,
startedAt: null,
progress: null,

This comment was marked as outdated.

Copy link
Owner Author

@AndyMik90 AndyMik90 left a comment

Choose a reason for hiding this comment

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

✅ Auto Claude Review - APPROVED

Status: Ready to Merge

Summary: ## 🟡 Follow-up Review: Merge With Changes

🟡 Can merge - Minor suggestions noted, no blockers.

Resolution Status

  • Resolved: 4 previous findings addressed
  • Unresolved: 1 previous findings remain
  • 🆕 New Issues: 5 new findings in recent changes

Finding Validation

  • 🔍 Dismissed as False Positives: 2 findings were re-investigated and found to be incorrect
  • Confirmed Valid: 7 findings verified as genuine issues
  • 👤 Needs Human Review: 0 findings require manual verification

Verdict

All 20 CI checks passing ✅. Excellent progress: all 3 blocking findings from the previous review (NEW-FOLLOWUP-ORDER high, NEW-CLEAR-REVIEW medium, CMT-FOLLOWUP-ORDER high) are fully resolved. 4 of 5 previous findings addressed; only NEW-005 (low, ipcMain.emit semantics) remains — functionally harmless since the listener ignores all arguments.

The most concerning new finding (buildContextKey deduplication suppressing UI updates after post/delete) was DISMISSED as a false positive after validation: the renderer-side code in useGitHubPRs.ts directly calls setLoadedReviewResult after these operations, updating the store independently of the XState IPC channel. The Sentry bot's startedAt concern was also dismissed — startedAt is not consumed in the completed state since isReviewing is false.

Remaining confirmed findings are all LOW severity: (1) stale progress bar during external review polling, (2) handleClearReview emits zeroed prNumber/projectId, (3) store entries accumulate without cleanup / clearPRReview is dead code, (4) silent return on duplicate follow-up review, (5) missing test coverage for CLEAR_REVIEW from reviewing state. None of these block merge — they are polish items that can be addressed in a follow-up.

Review Process

Agents invoked: resolution-verifier, new-code-reviewer, comment-analyzer, finding-validator


This is an AI-generated follow-up review using parallel specialist analysis with finding validation.


💡 Suggestions (6)

These are non-blocking suggestions for consideration:

🔵 [NEW-005] [LOW] [UNRESOLVED] [UNRESOLVED] ipcMain.emit uses EventEmitter semantics, not IPC conventions

📁 apps/frontend/src/main/ipc-handlers/github/oauth-handlers.ts:55

ipcMain.emit(channel, payload) passes payload directly as first argument (EventEmitter pattern), but ipcMain.on listeners normally receive (event, ...args) with IPC semantics. Works because the listener ignores all arguments, but could confuse future maintainers.

Resolution note: ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, payload) at line 55 of oauth-handlers.ts still uses EventEmitter semantics. The listener at pr-handlers.ts:1665 ignores all args (() => {}), so it works functionally, but the pattern remains technically incorrect.

Resolution note: ipcMain.emit(IPC_CHANNELS.GITHUB_AUTH_CHANGED, payload) at oauth-handlers.ts:55 still uses EventEmitter semantics. The listener at pr-handlers.ts:1666 uses () => (ignoring all args), so it works correctly. The pattern is semantically unconventional but functionally harmless.

Suggested fix:

Add a code comment explaining that this uses EventEmitter.emit (not IPC send) and the listener intentionally ignores args.

🔵 [REVIEW-NEW-002] [LOW] setExternalReview action does not clear stale progress data

📁 apps/frontend/src/shared/state-machines/pr-review-machine.ts:183

When transitioning from reviewing to externalReview via DETECT_EXTERNAL_REVIEW, the setExternalReview action only sets isExternalReview: true without clearing progress. Before this transition, progress is set to 100% with 'Review already in progress' message. This stale progress remains and is rendered as a misleading 100% progress bar while the app polls for the external review result.

Suggested fix:

Add progress: () => null to the setExternalReview assign action.

🔵 [REVIEW-NEW-003] [LOW] handleClearReview emits idle state with zeroed prNumber/projectId

📁 apps/frontend/src/main/pr-review-state-manager.ts:79

handleClearReview sends CLEAR_REVIEW which resets context to initial values (prNumber: null, projectId: null). The subscription fires with prNumber: 0 and projectId: '' in the payload. This contrasts with handleAuthChange which captures the snapshot before stopping to preserve real values. Functional impact is minimal since the store uses the key for indexing.

Suggested fix:

Capture the snapshot before sending CLEAR_REVIEW and use emitClearedState with the real context, similar to handleAuthChange.

🔵 [REVIEW-NEW-005] [LOW] Store entries accumulate indefinitely; clearPRReview is dead code

📁 apps/frontend/src/renderer/stores/github/pr-review-store.ts:83

handlePRReviewStateChange always creates or updates entries in prReviews, even for idle state payloads. clearPRReview is defined (line 194) but never called anywhere in the codebase (zero callers found). The only cleanup mechanism is the auth change bulk clear. Within a session, entries grow monotonically.

Suggested fix:

Either remove entries when idle state payload arrives, or remove the dead clearPRReview code and document that auth change is the only cleanup path.

🔵 [CMT-003] [LOW] Silent return on duplicate follow-up review gives no user feedback

📁 apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts:2905

When a follow-up review is already running and the user triggers another, the handler silently returns with only a debug log (line 2905-2907). The initial review handler (line 1883-1890) sends 'Review is already in progress. Reconnecting to ongoing review...' message. This inconsistency means users get no feedback for duplicate follow-up requests.

Suggested fix:

Add a sendProgress call before returning, similar to the initial review handler.

🔵 [CMT-004] [LOW] [FROM COMMENTS] Missing test coverage for CLEAR_REVIEW from reviewing and externalReview states

📁 apps/frontend/src/shared/state-machines/__tests__/pr-review-machine.test.ts:171

The state machine defines CLEAR_REVIEW transitions in 4 states (reviewing, externalReview, completed, error), but tests only cover CLEAR_REVIEW from completed and error states. The reviewing and externalReview states' CLEAR_REVIEW transitions are untested. The transition logic is identical across all states, so practical risk is low.

Suggested fix:

Add test cases: 'should clear from reviewing to idle' and 'should clear from externalReview to idle'.

This automated review found no blocking issues. The PR can be safely merged.

Generated by Auto Claude

AndyMik90 and others added 2 commits February 15, 2026 14:09
…, missing tests

- Clear stale progress data when transitioning to externalReview state
- Capture snapshot before CLEAR_REVIEW so emitted payload has real context
- Add user feedback for duplicate follow-up review requests
- Remove dead clearPRReview code from store (zero callers)
- Add test coverage for CLEAR_REVIEW from reviewing/externalReview states
- Clarify ipcMain.emit comment re: EventEmitter semantics

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/frontend This is frontend only size/XL Extra large (1000+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python subprocess hangs on import when spawned from Electron (macOS)

1 participant