Skip to content

Conversation

@AndyMik90
Copy link
Owner

@AndyMik90 AndyMik90 commented Dec 29, 2025

Base Branch

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

Description

Adds a "Create PR" button alongside Stage/Merge in the human review workflow. Users can select a target branch from a dropdown and create a GitHub PR directly from the review panel. The branch is auto-pushed to remote if needed, and a "PR Created" badge is displayed on the task card.

Related Issue

Closes #

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

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)

CI/Testing Requirements

  • All CI checks pass
  • All existing tests pass
  • New features include test coverage
  • Bug fixes include regression tests

Screenshots

Before After
Only Stage/Merge buttons Stage/Merge + Create PR button with branch selector

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

Implementation Details

Features

  • Create PR Button: New action button in the human review workflow
  • Branch Selector: Dropdown to select target branch with smart defaults:
    • Task's baseBranch if set (task-level override)
    • Project's defaultBranch setting
    • Falls back to main/master/develop
  • Auto-push: Automatically pushes branch to remote if not already pushed
  • PR Created Badge: Purple badge with GitPullRequest icon shown on task cards and detail modal
  • Task Status: Task stays in human_review status after PR creation (user decides next action)
  • i18n Support: English and French translations

Files Changed (15 files)

  • src/shared/types/task.ts - Added prUrl and prCreatedAt fields to Task interface
  • src/shared/types/ipc.ts - Added createPullRequest to ElectronAPI
  • src/shared/constants/ipc.ts - Added TASK_CREATE_PR channel
  • src/main/ipc-handlers/task/worktree-handlers.ts - PR creation handler using gh CLI
  • src/main/project-store.ts - Load PR info from implementation_plan.json
  • src/preload/api/task-api.ts - Exposed createPullRequest API
  • src/renderer/components/task-detail/hooks/useTaskDetail.ts - PR-related state
  • src/renderer/components/task-detail/task-review/WorkspaceStatus.tsx - Create PR UI
  • src/renderer/components/task-detail/TaskReview.tsx - Pass PR props
  • src/renderer/components/task-detail/TaskDetailModal.tsx - handleCreatePR + badge
  • src/renderer/components/task-detail/TaskDetailPanel.tsx - handleCreatePR + props
  • src/renderer/components/TaskCard.tsx - PR Created badge
  • src/shared/i18n/locales/en/tasks.json - English translations
  • src/shared/i18n/locales/fr/tasks.json - French translations
  • src/renderer/lib/mocks/workspace-mock.ts - Browser mock

Summary by CodeRabbit

Release Notes

  • New Features
    • Added ability to create pull requests directly from task worktrees with configurable target branch selection
    • Added PR status badge display on task cards
    • Added pull request URL and creation timestamp tracking to task metadata

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

Adds a "Create PR" button alongside Stage/Merge in the human review
workflow. Users can select a target branch and create a GitHub PR
directly from the review panel.

Features:
- Auto-push branch to remote if not already pushed
- Smart default branch selection (task baseBranch → project defaultBranch → main/master)
- "PR Created" badge displayed on task cards and detail modal
- Task stays in human_review status after PR creation (user decides next action)
- PR URL stored in implementation_plan.json for tracking
- i18n support (English and French)
@github-actions github-actions bot added area/frontend This is frontend only feature New feature or request size/L Large (500-999 lines) 🔄 Checking Checking PR Status labels Dec 29, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 29, 2025

📝 Walkthrough

Walkthrough

A new PR feature is added, enabling users to create pull requests directly from task worktrees. Changes span backend IPC handlers for PR creation with git/gh integration, project-store updates for PR metadata storage, frontend API layer wiring, UI components for branch selection and PR status display, state management via hooks, localization strings, and type definitions.

Changes

Cohort / File(s) Summary
IPC Handler & Backend
apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts
Added new TASK_CREATE_PR handler for creating PRs from worktrees; validates task/worktree, ensures git/gh availability, pushes branch if needed, extracts PR URL and number, updates implementation_plan.json metadata asynchronously. ⚠️ Note: handler appears twice in file (duplication).
Project Store
apps/frontend/src/main/project-store.ts
Extended plan data extraction to include PR-related fields (prUrl, prCreatedAt) alongside existing staged metadata; propagated into task objects for plan updates.
IPC Constants & Types
apps/frontend/src/shared/constants/ipc.ts, apps/frontend/src/shared/types/ipc.ts, apps/frontend/src/shared/types/task.ts
Added TASK_CREATE_PR channel constant; introduced CreatePullRequestResult type with prUrl, prNumber, wasPushed; extended Task interface with optional prUrl and prCreatedAt fields; updated ElectronAPI signature.
Frontend API Layer
apps/frontend/src/preload/api/task-api.ts
Added createPullRequest(taskId, targetBranch) method to TaskAPI; wired IPC invocation for TASK_CREATE_PR channel.
Task Detail Components
apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx, apps/frontend/src/renderer/components/task-detail/TaskDetailPanel.tsx
Introduced handleCreatePR validation/execution logic; manage PR state (isCreatingPR, showPRBranchSelector, prTargetBranch); propagate PR-related props to child components.
UI Components
apps/frontend/src/renderer/components/task-detail/TaskReview.tsx, apps/frontend/src/renderer/components/task-detail/task-review/WorkspaceStatus.tsx
Extended TaskReview props to accept PR state/handlers; WorkspaceStatus adds PR creation UI (branch selector dropdown, create PR button with loading state, PR success message with external link). Existing merge/discard actions disabled during PR creation.
Task Card
apps/frontend/src/renderer/components/TaskCard.tsx
Added GitPullRequest icon and PR Created badge display when task.prUrl is present.
State Management
apps/frontend/src/renderer/components/task-detail/hooks/useTaskDetail.ts
Introduced PR-related state (isCreatingPR, showPRBranchSelector, prTargetBranch, availableBranches, prSuccess); loads branches via getGitBranches; exposes state and setters in hook return.
Mocks
apps/frontend/src/renderer/lib/mocks/workspace-mock.ts
Added createPullRequest mock method returning success payload with PR details (prUrl, prNumber, wasPushed).
Localization
apps/frontend/src/shared/i18n/locales/en/tasks.json, apps/frontend/src/shared/i18n/locales/fr/tasks.json
Added prCreated label and new review section with PR-related keys (createPR, createPRTitle, targetBranch, selectBranch, creatingPR, viewPR, prCreated, prAlreadyExists, ghNotInstalled, pushFailed, noCommits) for both English and French.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as TaskDetailModal/<br/>WorkspaceStatus
    participant Hook as useTaskDetail
    participant API as TaskAPI
    participant Main as Main Process<br/>(IPC Handler)
    participant Git as Git/GH CLI
    participant Fs as File System
    
    User->>UI: Click "Create PR"
    activate UI
    UI->>UI: Validate target branch
    UI->>Hook: setIsCreatingPR(true)
    activate Hook
    Hook-->>UI: Update state
    deactivate Hook
    
    UI->>API: createPullRequest(taskId, branch)
    activate API
    API->>Main: IPC invoke TASK_CREATE_PR
    deactivate API
    
    activate Main
    Main->>Main: Validate task & worktree
    Main->>Git: Get current branch
    Main->>Git: Ensure upstream set
    Main->>Git: Push branch (if needed)
    
    rect rgba(76, 175, 80, 0.2)
    Note over Main,Git: PR Creation Phase
    Main->>Git: gh pr create --body...
    Git-->>Main: PR URL & number
    end
    
    Main->>Fs: Write to implementation_plan.json<br/>(async, best-effort)
    Main-->>API: Return PR details
    deactivate Main
    
    activate UI
    UI->>Hook: setPrSuccess({prUrl, message})
    activate Hook
    Hook-->>UI: Update state
    deactivate Hook
    UI->>Hook: setShowPRBranchSelector(false)
    UI->>Hook: setIsCreatingPR(false)
    UI->>UI: Display PR Created badge
    deactivate UI
    
    User->>UI: View PR (click link)
    activate UI
    UI->>UI: Open prUrl in browser
    deactivate UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

feature, area/frontend, size/M

Suggested reviewers

  • AlexMadera

Poem

🐰 A hop, a branch, a PR so fine,
From worktree depths to GitHub's shine,
With badge and button, status clear,
Pull requests bloom—hooray, hooray, my dear! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(review): add Create PR option to human review workflow' accurately and clearly summarizes the main change: introducing a Create PR feature in the human review workflow. The title uses conventional commit format and is specific enough to convey the primary purpose.
✨ Finishing touches
  • 📝 Generate docstrings

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

plan.prUrl = prUrl;
plan.prCreatedAt = new Date().toISOString();
plan.updated_at = new Date().toISOString();
writeFileSync(planPath, JSON.stringify(plan, null, 2));

Check failure

Code scanning / CodeQL

Potential file system race condition High

The file may have changed since it
was checked
.
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: 7

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a39ea49 and 2027ac8.

📒 Files selected for processing (15)
  • apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts
  • apps/frontend/src/main/project-store.ts
  • apps/frontend/src/preload/api/task-api.ts
  • apps/frontend/src/renderer/components/TaskCard.tsx
  • apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx
  • apps/frontend/src/renderer/components/task-detail/TaskDetailPanel.tsx
  • apps/frontend/src/renderer/components/task-detail/TaskReview.tsx
  • apps/frontend/src/renderer/components/task-detail/hooks/useTaskDetail.ts
  • apps/frontend/src/renderer/components/task-detail/task-review/WorkspaceStatus.tsx
  • apps/frontend/src/renderer/lib/mocks/workspace-mock.ts
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/i18n/locales/en/tasks.json
  • apps/frontend/src/shared/i18n/locales/fr/tasks.json
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/shared/types/task.ts
🧰 Additional context used
📓 Path-based instructions (3)
apps/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/frontend/src/**/*.{ts,tsx}: Always use translation keys with useTranslation() for all user-facing text in React/TypeScript frontend components - use format namespace:section.key (e.g., navigation:items.githubPRs)
Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from apps/frontend/src/shared/i18n/locales/

Files:

  • apps/frontend/src/main/project-store.ts
  • apps/frontend/src/renderer/lib/mocks/workspace-mock.ts
  • apps/frontend/src/preload/api/task-api.ts
  • apps/frontend/src/shared/types/task.ts
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/renderer/components/task-detail/hooks/useTaskDetail.ts
  • apps/frontend/src/renderer/components/task-detail/TaskReview.tsx
  • apps/frontend/src/renderer/components/task-detail/task-review/WorkspaceStatus.tsx
  • apps/frontend/src/renderer/components/task-detail/TaskDetailPanel.tsx
  • apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts
  • apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx
  • apps/frontend/src/renderer/components/TaskCard.tsx
apps/frontend/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

apps/frontend/**/*.{ts,tsx}: Review React patterns and TypeScript type safety.
Check for proper state management and component composition.

Files:

  • apps/frontend/src/main/project-store.ts
  • apps/frontend/src/renderer/lib/mocks/workspace-mock.ts
  • apps/frontend/src/preload/api/task-api.ts
  • apps/frontend/src/shared/types/task.ts
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/renderer/components/task-detail/hooks/useTaskDetail.ts
  • apps/frontend/src/renderer/components/task-detail/TaskReview.tsx
  • apps/frontend/src/renderer/components/task-detail/task-review/WorkspaceStatus.tsx
  • apps/frontend/src/renderer/components/task-detail/TaskDetailPanel.tsx
  • apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts
  • apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx
  • apps/frontend/src/renderer/components/TaskCard.tsx
apps/frontend/src/shared/i18n/locales/**/*.json

📄 CodeRabbit inference engine (CLAUDE.md)

Add new translation keys to ALL language files (minimum: en/*.json and fr/*.json) in apps/frontend/src/shared/i18n/locales/

Files:

  • apps/frontend/src/shared/i18n/locales/en/tasks.json
  • apps/frontend/src/shared/i18n/locales/fr/tasks.json
🧠 Learnings (2)
📚 Learning: 2025-12-25T18:29:32.954Z
Learnt from: CR
Repo: AndyMik90/Auto-Claude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-25T18:29:32.954Z
Learning: Applies to apps/frontend/src/shared/i18n/locales/**/*.json : Add new translation keys to ALL language files (minimum: `en/*.json` and `fr/*.json`) in `apps/frontend/src/shared/i18n/locales/`

Applied to files:

  • apps/frontend/src/shared/i18n/locales/en/tasks.json
  • apps/frontend/src/shared/i18n/locales/fr/tasks.json
📚 Learning: 2025-12-25T18:29:32.954Z
Learnt from: CR
Repo: AndyMik90/Auto-Claude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-25T18:29:32.954Z
Learning: Applies to apps/frontend/src/**/*.{ts,tsx} : Always use translation keys with `useTranslation()` for all user-facing text in React/TypeScript frontend components - use format `namespace:section.key` (e.g., `navigation:items.githubPRs`)

Applied to files:

  • apps/frontend/src/renderer/components/TaskCard.tsx
🧬 Code graph analysis (6)
apps/frontend/src/preload/api/task-api.ts (1)
apps/frontend/src/shared/constants/ipc.ts (1)
  • IPC_CHANNELS (6-378)
apps/frontend/src/shared/types/ipc.ts (1)
apps/frontend/src/shared/types/task.ts (1)
  • CreatePullRequestResult (409-415)
apps/frontend/src/renderer/components/task-detail/task-review/WorkspaceStatus.tsx (1)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
apps/frontend/src/renderer/components/task-detail/TaskDetailPanel.tsx (1)
apps/frontend/src/renderer/components/settings/utils/hookProxyFactory.ts (1)
  • error (18-18)
apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx (1)
.design-system/src/components/Badge.tsx (1)
  • Badge (9-27)
apps/frontend/src/renderer/components/TaskCard.tsx (1)
.design-system/src/components/Badge.tsx (1)
  • Badge (9-27)
🪛 GitHub Check: CodeQL
apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts

[failure] 2313-2313: Potential file system race condition
The file may have changed since it was checked.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: CodeQL (python)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (17)
apps/frontend/src/preload/api/task-api.ts (2)

54-54: LGTM! Clean API addition.

The new createPullRequest method is properly typed and follows the established pattern for IPC communication in this codebase.


145-146: LGTM! Proper IPC wiring.

The implementation correctly invokes the IPC channel with the expected parameters and return type.

apps/frontend/src/renderer/components/TaskCard.tsx (1)

257-266: LGTM! Proper i18n usage and consistent styling.

The PR Created badge correctly uses the translation key t('labels.prCreated') as per coding guidelines, and the styling is consistent with other status badges in the component.

As per coding guidelines, the translation key must exist in all language files. This will be verified in the review of the locale files.

apps/frontend/src/shared/constants/ipc.ts (1)

42-42: LGTM! Clean constant addition.

The new IPC channel constant follows the established naming convention and is properly documented with an inline comment.

apps/frontend/src/shared/types/task.ts (2)

253-254: LGTM! Clean type extension.

The new optional fields prUrl and prCreatedAt are properly integrated into the Task interface and align with the PR creation feature requirements.


406-415: LGTM! Well-documented interface.

The CreatePullRequestResult interface is properly defined with clear JSDoc comments and appropriate field types. The optional wasPushed field provides useful context about the operation.

apps/frontend/src/shared/types/ipc.ts (2)

41-41: LGTM! Proper type import.

The CreatePullRequestResult type is correctly imported from the task types module.


153-153: LGTM! Clean API signature.

The createPullRequest method signature is properly typed and consistent with other workspace management operations in the ElectronAPI interface.

apps/frontend/src/shared/i18n/locales/en/tasks.json (1)

29-29: All French translations are already present in apps/frontend/src/shared/i18n/locales/fr/tasks.json. All 12 new keys have been added with proper French translations:

  • labels.prCreated: "PR créée"
  • review.createPR, createPRTitle, targetBranch, selectBranch, creatingPR, viewPR, prCreated, prAlreadyExists, ghNotInstalled, pushFailed, noCommits are all present with appropriate French translations.

No action required.

apps/frontend/src/shared/i18n/locales/fr/tasks.json (1)

87-99: LGTM! French translations for PR feature are complete.

The new review section properly provides French translations for all PR-related strings. The translations appear accurate and consistent with the existing French localization style. Based on learnings, this aligns with the requirement to add translation keys to all language files.

apps/frontend/src/renderer/lib/mocks/workspace-mock.ts (1)

94-103: LGTM! Mock correctly simulates PR creation response.

The mock method signature and return structure align with the CreatePullRequestResult type and will support testing of the PR creation flow without requiring actual GitHub integration.

apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx (1)

155-178: LGTM! PR creation handler is well-structured.

The handleCreatePR function properly:

  • Validates target branch before proceeding
  • Manages loading state with isCreatingPR
  • Clears errors before the operation
  • Uses try/catch/finally pattern for proper cleanup
  • Handles both success and error cases appropriately
apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts (1)

2215-2267: LGTM! PR creation handler with proper git workflow.

The handler correctly:

  • Validates task and worktree existence
  • Gets current branch from worktree
  • Checks for upstream tracking and auto-pushes if needed
  • Provides clear error messages for push failures
apps/frontend/src/renderer/components/task-detail/TaskReview.tsx (2)

45-53: LGTM! PR props interface is well-defined.

The new PR-related props are appropriately marked as optional, allowing backward compatibility with existing usages. The types correctly match the expected shapes from the parent components.


140-148: LGTM! Clean prop forwarding to WorkspaceStatus.

The PR-related props are correctly forwarded to the child component, maintaining the component's role as a composition layer without adding logic.

apps/frontend/src/renderer/components/task-detail/task-review/WorkspaceStatus.tsx (2)

428-440: LGTM! PR button with proper i18n and state management.

The Create PR button correctly:

  • Uses translation key t('tasks:review.createPR')
  • Disables during merge, discard, or PR creation operations
  • Toggles the branch selector visibility

As per coding guidelines, user-facing text is properly internationalized.


453-507: LGTM! PR branch selector UI is well-implemented.

The branch selector panel:

  • Uses translation keys consistently for all text
  • Properly manages disabled states during PR creation
  • Provides clear cancel action
  • Uses Radix Select component correctly with proper placeholder

Comment on lines +2269 to +2276
// Check if gh CLI is available
const ghPath = getToolPath('gh');
if (!ghPath) {
return {
success: false,
error: 'GitHub CLI (gh) not found. Install it from https://cli.github.com/'
};
}
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

🧩 Analysis chain

🏁 Script executed:

# First, read the specific file and surrounding context
wc -l apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 131


🏁 Script executed:

# Read the context around lines 2269-2276
sed -n '2250,2350p' apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 3551


🏁 Script executed:

# Search for gh command usage and error handling patterns in this file
rg -n 'gh\s+(pr|auth|status)' apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts -A 3

Repository: AndyMik90/Auto-Claude

Length of output: 393


🏁 Script executed:

# Check if there are any existing authentication checks in the codebase
rg -n 'gh auth status' apps/frontend/src/main/ipc-handlers/task/ -A 2

Repository: AndyMik90/Auto-Claude

Length of output: 47


Add GitHub CLI authentication verification before PR creation.

The handler checks if gh is installed but doesn't verify authentication status. If gh is installed but not authenticated, the gh pr create command fails with a generic error message rather than clearly indicating an authentication issue.

Consider adding an explicit gh auth status check before attempting PR creation, or add specific error pattern matching to detect and report authentication failures with a clear message like "GitHub CLI not authenticated. Run 'gh auth login' to authenticate."

🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts around lines
2269 to 2276, the code only checks that the GitHub CLI binary exists but does
not verify the user is authenticated; add a pre-check that runs "gh auth status"
(or equivalent) and treat non-zero exit or auth-specific output as
unauthenticated, returning a clear error like "GitHub CLI not authenticated. Run
'gh auth login' to authenticate."; additionally, when running gh pr create,
catch and pattern-match auth-related errors and convert them to the same clear
message so callers get an actionable response.

Comment on lines +2307 to +2318
if (existsSync(planPath)) {
try {
const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
plan.prUrl = prUrl;
plan.prCreatedAt = new Date().toISOString();
plan.updated_at = new Date().toISOString();
writeFileSync(planPath, JSON.stringify(plan, null, 2));
} catch (planError) {
console.error('Failed to update implementation plan:', planError);
// Don't fail the PR creation just because plan update failed
}
}
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

Fix TOCTOU race condition in plan file update.

The existsSync check at line 2307 followed by readFileSync/writeFileSync creates a time-of-check to time-of-use race condition. The file could be modified or deleted between the check and the actual file operations.

🔎 Proposed fix using EAFP pattern
-          if (existsSync(planPath)) {
-            try {
-              const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
-              plan.prUrl = prUrl;
-              plan.prCreatedAt = new Date().toISOString();
-              plan.updated_at = new Date().toISOString();
-              writeFileSync(planPath, JSON.stringify(plan, null, 2));
-            } catch (planError) {
-              console.error('Failed to update implementation plan:', planError);
-              // Don't fail the PR creation just because plan update failed
-            }
+          try {
+            const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
+            plan.prUrl = prUrl;
+            plan.prCreatedAt = new Date().toISOString();
+            plan.updated_at = new Date().toISOString();
+            writeFileSync(planPath, JSON.stringify(plan, null, 2));
+          } catch (planError) {
+            // File may not exist or could have been modified - this is non-critical
+            if ((planError as NodeJS.ErrnoException).code !== 'ENOENT') {
+              console.error('Failed to update implementation plan:', planError);
+            }
+            // Don't fail the PR creation just because plan update failed
           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (existsSync(planPath)) {
try {
const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
plan.prUrl = prUrl;
plan.prCreatedAt = new Date().toISOString();
plan.updated_at = new Date().toISOString();
writeFileSync(planPath, JSON.stringify(plan, null, 2));
} catch (planError) {
console.error('Failed to update implementation plan:', planError);
// Don't fail the PR creation just because plan update failed
}
}
try {
const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
plan.prUrl = prUrl;
plan.prCreatedAt = new Date().toISOString();
plan.updated_at = new Date().toISOString();
writeFileSync(planPath, JSON.stringify(plan, null, 2));
} catch (planError) {
// File may not exist or could have been modified - this is non-critical
if ((planError as NodeJS.ErrnoException).code !== 'ENOENT') {
console.error('Failed to update implementation plan:', planError);
}
// Don't fail the PR creation just because plan update failed
}
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 2313-2313: Potential file system race condition
The file may have changed since it was checked.

🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts around lines
2307-2318, replace the existsSync+readFileSync/writeFileSync sequence (TOCTOU)
with an EAFP approach: attempt to read and parse the file inside a try/catch
(handle ENOENT by treating the file as absent and not failing PR creation),
update the parsed object if successful, and write the updated content atomically
by writing to a temporary file in the same directory and renaming it over the
original; ensure JSON parse errors and write errors are caught and logged but do
not abort PR creation.

Comment on lines +425 to +430
// Extract staged status and PR info from plan
const planWithExtras = plan as unknown as { stagedInMainProject?: boolean; stagedAt?: string; prUrl?: string; prCreatedAt?: string } | null;
const stagedInMainProject = planWithExtras?.stagedInMainProject;
const stagedAt = planWithExtras?.stagedAt;
const prUrl = planWithExtras?.prUrl;
const prCreatedAt = planWithExtras?.prCreatedAt;
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

Consider safer type handling for plan extensions.

The type assertion as unknown as bypasses TypeScript's type safety. While the code works because all fields are optional and safely accessed, this pattern could mask future issues if the plan structure changes.

💡 Suggested approach using type guards or extending the ImplementationPlan type

Option 1: Extend ImplementationPlan type (if this is now part of the standard structure)

In apps/frontend/src/shared/types/task.ts:

export interface ImplementationPlan {
  // ... existing fields
  stagedInMainProject?: boolean;
  stagedAt?: string;
  prUrl?: string;
  prCreatedAt?: string;
}

Then no type assertion is needed:

-// Extract staged status and PR info from plan
-const planWithExtras = plan as unknown as { stagedInMainProject?: boolean; stagedAt?: string; prUrl?: string; prCreatedAt?: string } | null;
-const stagedInMainProject = planWithExtras?.stagedInMainProject;
-const stagedAt = planWithExtras?.stagedAt;
-const prUrl = planWithExtras?.prUrl;
-const prCreatedAt = planWithExtras?.prCreatedAt;
+// Extract staged status and PR info from plan
+const stagedInMainProject = plan?.stagedInMainProject;
+const stagedAt = plan?.stagedAt;
+const prUrl = plan?.prUrl;
+const prCreatedAt = plan?.prCreatedAt;

Option 2: Use type guard (if these fields are dynamically added)

// Helper function at top of file
function hasPRInfo(plan: ImplementationPlan | null): plan is ImplementationPlan & { 
  stagedInMainProject?: boolean; 
  stagedAt?: string; 
  prUrl?: string; 
  prCreatedAt?: string;
} {
  return plan !== null;
}

// Then use:
const prUrl = hasPRInfo(plan) ? plan.prUrl : undefined;
const prCreatedAt = hasPRInfo(plan) ? plan.prCreatedAt : undefined;

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

🤖 Prompt for AI Agents
apps/frontend/src/main/project-store.ts lines 425-430: the code unsafely uses an
`as unknown as` assertion to read optional PR/staging fields from `plan`; either
extend the shared ImplementationPlan type
(apps/frontend/src/shared/types/task.ts) to include stagedInMainProject?,
stagedAt?, prUrl?, prCreatedAt? and remove the assertion, or add a narrow type
guard that checks plan is non-null/has those optional keys and use that guard
before reading the properties, replacing the `as unknown as` pattern with the
safer typed access.

Comment on lines +138 to +169
// Load available branches for PR creation when task is in human_review
useEffect(() => {
if (needsReview && selectedProject) {
// Get branches
window.electronAPI.getGitBranches(selectedProject.path).then((result) => {
if (result.success && result.data) {
setAvailableBranches(result.data);

// Set default target branch:
// 1. Task's baseBranch if set
// 2. Project's defaultBranch
// 3. Detected main branch (main/master/develop)
const taskBaseBranch = task.metadata?.baseBranch;
if (taskBaseBranch && result.data.includes(taskBaseBranch)) {
setPrTargetBranch(taskBaseBranch);
} else {
window.electronAPI.getProjectEnv(selectedProject.id).then((envResult) => {
if (envResult.success && envResult.data?.defaultBranch && result.data?.includes(envResult.data.defaultBranch)) {
setPrTargetBranch(envResult.data.defaultBranch);
} else {
// Use main/master/develop as fallback
const mainBranch = result.data?.find(b => ['main', 'master', 'develop'].includes(b));
if (mainBranch) {
setPrTargetBranch(mainBranch);
}
}
});
}
}
});
}
}, [needsReview, selectedProject, task.metadata?.baseBranch]);
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

Add error handling and simplify nested promise chain.

The nested promise chain lacks error handling, which means failures from getGitBranches or getProjectEnv will silently fail without notifying the user. Additionally, the deeply nested structure makes the logic harder to follow and maintain.

🔎 Proposed refactor with error handling and flattened structure
  // Load available branches for PR creation when task is in human_review
  useEffect(() => {
    if (needsReview && selectedProject) {
-     // Get branches
-     window.electronAPI.getGitBranches(selectedProject.path).then((result) => {
-       if (result.success && result.data) {
-         setAvailableBranches(result.data);
-
-         // Set default target branch:
-         // 1. Task's baseBranch if set
-         // 2. Project's defaultBranch
-         // 3. Detected main branch (main/master/develop)
-         const taskBaseBranch = task.metadata?.baseBranch;
-         if (taskBaseBranch && result.data.includes(taskBaseBranch)) {
-           setPrTargetBranch(taskBaseBranch);
-         } else {
-           window.electronAPI.getProjectEnv(selectedProject.id).then((envResult) => {
-             if (envResult.success && envResult.data?.defaultBranch && result.data?.includes(envResult.data.defaultBranch)) {
-               setPrTargetBranch(envResult.data.defaultBranch);
-             } else {
-               // Use main/master/develop as fallback
-               const mainBranch = result.data?.find(b => ['main', 'master', 'develop'].includes(b));
-               if (mainBranch) {
-                 setPrTargetBranch(mainBranch);
-               }
-             }
-           });
-         }
-       }
-     });
+     const loadBranchesAndDefaults = async () => {
+       try {
+         const result = await window.electronAPI.getGitBranches(selectedProject.path);
+         if (!result.success || !result.data) {
+           console.error('[useTaskDetail] Failed to load branches:', result.error);
+           return;
+         }
+
+         setAvailableBranches(result.data);
+
+         // Set default target branch with priority:
+         // 1. Task's baseBranch if set
+         const taskBaseBranch = task.metadata?.baseBranch;
+         if (taskBaseBranch && result.data.includes(taskBaseBranch)) {
+           setPrTargetBranch(taskBaseBranch);
+           return;
+         }
+
+         // 2. Project's defaultBranch
+         const envResult = await window.electronAPI.getProjectEnv(selectedProject.id);
+         if (envResult.success && envResult.data?.defaultBranch && result.data.includes(envResult.data.defaultBranch)) {
+           setPrTargetBranch(envResult.data.defaultBranch);
+           return;
+         }
+
+         // 3. Detected main branch (main/master/develop)
+         const mainBranch = result.data.find(b => ['main', 'master', 'develop'].includes(b));
+         if (mainBranch) {
+           setPrTargetBranch(mainBranch);
+         }
+       } catch (err) {
+         console.error('[useTaskDetail] Error loading branches:', err);
+       }
+     };
+
+     loadBranchesAndDefaults();
    }
  }, [needsReview, selectedProject, task.metadata?.baseBranch]);
📝 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
// Load available branches for PR creation when task is in human_review
useEffect(() => {
if (needsReview && selectedProject) {
// Get branches
window.electronAPI.getGitBranches(selectedProject.path).then((result) => {
if (result.success && result.data) {
setAvailableBranches(result.data);
// Set default target branch:
// 1. Task's baseBranch if set
// 2. Project's defaultBranch
// 3. Detected main branch (main/master/develop)
const taskBaseBranch = task.metadata?.baseBranch;
if (taskBaseBranch && result.data.includes(taskBaseBranch)) {
setPrTargetBranch(taskBaseBranch);
} else {
window.electronAPI.getProjectEnv(selectedProject.id).then((envResult) => {
if (envResult.success && envResult.data?.defaultBranch && result.data?.includes(envResult.data.defaultBranch)) {
setPrTargetBranch(envResult.data.defaultBranch);
} else {
// Use main/master/develop as fallback
const mainBranch = result.data?.find(b => ['main', 'master', 'develop'].includes(b));
if (mainBranch) {
setPrTargetBranch(mainBranch);
}
}
});
}
}
});
}
}, [needsReview, selectedProject, task.metadata?.baseBranch]);
// Load available branches for PR creation when task is in human_review
useEffect(() => {
if (needsReview && selectedProject) {
const loadBranchesAndDefaults = async () => {
try {
const result = await window.electronAPI.getGitBranches(selectedProject.path);
if (!result.success || !result.data) {
console.error('[useTaskDetail] Failed to load branches:', result.error);
return;
}
setAvailableBranches(result.data);
// Set default target branch with priority:
// 1. Task's baseBranch if set
const taskBaseBranch = task.metadata?.baseBranch;
if (taskBaseBranch && result.data.includes(taskBaseBranch)) {
setPrTargetBranch(taskBaseBranch);
return;
}
// 2. Project's defaultBranch
const envResult = await window.electronAPI.getProjectEnv(selectedProject.id);
if (envResult.success && envResult.data?.defaultBranch && result.data.includes(envResult.data.defaultBranch)) {
setPrTargetBranch(envResult.data.defaultBranch);
return;
}
// 3. Detected main branch (main/master/develop)
const mainBranch = result.data.find(b => ['main', 'master', 'develop'].includes(b));
if (mainBranch) {
setPrTargetBranch(mainBranch);
}
} catch (err) {
console.error('[useTaskDetail] Error loading branches:', err);
}
};
loadBranchesAndDefaults();
}
}, [needsReview, selectedProject, task.metadata?.baseBranch]);
🤖 Prompt for AI Agents
In apps/frontend/src/renderer/components/task-detail/hooks/useTaskDetail.ts
around lines 138-169, the current nested promise chain for getGitBranches and
getProjectEnv has no error handling and is deeply nested; refactor the effect to
an async IIFE (or separate async function) with try/catch to handle and log (or
surface) errors from both API calls, flatten the logic by awaiting
getGitBranches first, setAvailableBranches when successful, then determine pr
target branch by checking task.metadata?.baseBranch, then
project.env.defaultBranch, then fallback to main/master/develop, and include a
mounted/aborted flag to avoid state updates on unmounted components; ensure all
promise rejections are caught and handled (e.g., set an error state or notify
the user) and remove the nested .then chains.

Comment on lines +517 to +525
<Button
variant="ghost"
size="sm"
className="h-7 px-2"
onClick={() => window.electronAPI.openExternal(prSuccess.prUrl)}
>
<ExternalLink className="h-3.5 w-3.5 mr-1" />
{t('tasks:review.viewPR')}
</Button>
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

Add error handling for openExternal call.

The window.electronAPI.openExternal(prSuccess.prUrl) call at line 521 is invoked without error handling. If the URL is malformed or the external handler fails, the error will be silently swallowed.

🔎 Proposed fix
                 <Button
                   variant="ghost"
                   size="sm"
                   className="h-7 px-2"
-                  onClick={() => window.electronAPI.openExternal(prSuccess.prUrl)}
+                  onClick={async () => {
+                    try {
+                      await window.electronAPI.openExternal(prSuccess.prUrl);
+                    } catch (err) {
+                      console.error('Failed to open PR URL:', err);
+                    }
+                  }}
                 >
🤖 Prompt for AI Agents
In
apps/frontend/src/renderer/components/task-detail/task-review/WorkspaceStatus.tsx
around lines 517 to 525, the call to
window.electronAPI.openExternal(prSuccess.prUrl) is invoked without any error
handling; wrap the openExternal invocation in a try/catch (or handle the
returned Promise with .catch) and validate the URL before calling, and on error
log the failure and surface a user-friendly notification/toast so failures
aren’t silently swallowed. Ensure the handler is synchronous-friendly (use async
onClick if awaiting) and include the URL in the error log for debugging.

Comment on lines +320 to +328
{task.prUrl && (
<Badge
variant="outline"
className="text-xs flex items-center gap-1 bg-purple-500/10 text-purple-400 border-purple-500/30"
>
<GitPullRequest className="h-3 w-3" />
PR Created
</Badge>
)}
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

Use translation key for user-facing text.

The "PR Created" text on line 326 is hardcoded. As per coding guidelines, all user-facing text in React components should use useTranslation() with translation keys.

🔎 Proposed fix
+import { useTranslation } from 'react-i18next';
// ... in component:
+const { t } = useTranslation('tasks');
// ... in the badge:
                           {task.prUrl && (
                             <Badge
                               variant="outline"
                               className="text-xs flex items-center gap-1 bg-purple-500/10 text-purple-400 border-purple-500/30"
                             >
                               <GitPullRequest className="h-3 w-3" />
-                              PR Created
+                              {t('labels.prCreated')}
                             </Badge>
                           )}

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

🤖 Prompt for AI Agents
In apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx around
lines 320 to 328, the "PR Created" string is hardcoded; replace it with a
translation key using the useTranslation() hook (e.g. const { t } =
useTranslation()) and call t('task.prCreated') (or your chosen key) where the
text is rendered; ensure the hook is imported from react-i18next, update the
component to use t, and add the corresponding key/value to the locale JSON files
so translations exist.

Comment on lines +118 to +141
const handleCreatePR = async () => {
if (!state.prTargetBranch) {
state.setWorkspaceError('Please select a target branch');
return;
}
state.setIsCreatingPR(true);
state.setWorkspaceError(null);
try {
const result = await window.electronAPI.createPullRequest(task.id, state.prTargetBranch);
if (result.success && result.data?.success) {
state.setPrSuccess({
prUrl: result.data.prUrl || '',
message: result.data.message
});
state.setShowPRBranchSelector(false);
} else {
state.setWorkspaceError(result.data?.message || result.error || 'Failed to create pull request');
}
} catch (error) {
state.setWorkspaceError(error instanceof Error ? error.message : 'Unknown error creating PR');
} finally {
state.setIsCreatingPR(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

Consider extracting duplicated PR creation logic.

The handleCreatePR function is nearly identical to the one in TaskDetailModal.tsx (lines 155-178). This violates the DRY principle and creates maintenance burden.

Consider extracting this logic to either:

  1. A custom hook (e.g., usePRCreation)
  2. The existing useTaskDetail hook which already manages PR-related state
🔎 Suggested approach
// In useTaskDetail.ts or a new usePRCreation.ts hook:
const handleCreatePR = useCallback(async () => {
  if (!prTargetBranch) {
    setWorkspaceError('Please select a target branch');
    return;
  }
  setIsCreatingPR(true);
  setWorkspaceError(null);
  try {
    const result = await window.electronAPI.createPullRequest(task.id, prTargetBranch);
    if (result.success && result.data?.success) {
      setPrSuccess({
        prUrl: result.data.prUrl || '',
        message: result.data.message
      });
      setShowPRBranchSelector(false);
    } else {
      setWorkspaceError(result.data?.message || result.error || 'Failed to create pull request');
    }
  } catch (error) {
    setWorkspaceError(error instanceof Error ? error.message : 'Unknown error creating PR');
  } finally {
    setIsCreatingPR(false);
  }
}, [task.id, prTargetBranch, /* other deps */]);
🤖 Prompt for AI Agents
In apps/frontend/src/renderer/components/task-detail/TaskDetailPanel.tsx around
lines 118 to 141, the PR creation logic is duplicated (also present in
TaskDetailModal.tsx) — extract it into a shared hook (e.g., usePRCreation or add
to existing useTaskDetail) that accepts task.id and exposes handleCreatePR plus
the PR-related state setters (isCreatingPR, workspaceError, prSuccess,
showPRBranchSelector, prTargetBranch) so both components call the same callback;
implement the hook using useCallback with correct dependencies (task.id,
prTargetBranch, state setters), move the try/catch/finally logic there, and
replace the inline function in both components with the hook's handleCreatePR to
remove duplication and keep behavior identical.

const [showPRBranchSelector, setShowPRBranchSelector] = useState(false);
const [prTargetBranch, setPrTargetBranch] = useState<string>('');
const [availableBranches, setAvailableBranches] = useState<string[]>([]);
const [prSuccess, setPrSuccess] = useState<{ prUrl: string; message: string } | null>(null);
Copy link

Choose a reason for hiding this comment

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

PR success state persists incorrectly when switching tasks

The PR-related state variables (prSuccess, showPRBranchSelector) are not reset when task.id changes. The TaskDetailModal is rendered without a key prop, so when a user clicks a different task while the modal is open, the same component instance receives a new task prop but useState values persist. If a user creates a PR for task A, then clicks task B, the success message from task A will incorrectly appear in task B's view. The codebase already has a pattern for handling this issue (lines 238-246 reset mergePreview when task.id changes), but this pattern wasn't applied to the new PR state.

Fix in Cursor Fix in Web

>
<GitPullRequest className="h-3 w-3" />
PR Created
</Badge>
Copy link

Choose a reason for hiding this comment

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

Hardcoded PR badge text breaks localization

The PR badge text "PR Created" is hardcoded instead of using the i18n translation key. In TaskCard.tsx, the same badge correctly uses {t('labels.prCreated')} which displays "PR créée" for French users. However, TaskDetailModal.tsx has the hardcoded English string, breaking the localization feature. French users would see inconsistent language between the task card ("PR créée") and the detail modal ("PR Created").

Fix in Cursor Fix in Web

@AndyMik90 AndyMik90 self-assigned this Dec 29, 2025
@AlexMadera AlexMadera self-assigned this Dec 30, 2025
@AndyMik90 AndyMik90 added this to the Release 2.8 milestone Dec 30, 2025
Copy link
Collaborator

@AlexMadera AlexMadera 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: 🟡 MERGE WITH CHANGES

2 high-priority issues to address

Risk Assessment

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

Findings Summary

  • High: 2 issue(s)
  • Medium: 5 issue(s)
  • Low: 2 issue(s)

Generated by Auto Claude PR Review

Findings (9 selected of 9 total)

🟠 [HIGH] Insufficient sanitization of task.title for gh CLI

📁 apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts:2298

task.title is passed directly to 'gh pr create --title' without validation. While execFileSync with array arguments prevents shell injection, a malicious title containing newlines, null bytes, or extremely long strings could cause issues with gh CLI or DoS. Titles should be sanitized: stripped of control characters, length-limited (256 chars), and validated for printable characters only.

Suggested fix:

Sanitize title before use: const sanitizedTitle = task.title.replace(/[\r\n\0]/g, '').substring(0, 256).trim(); Validate with /^[\x20-\x7E]+$/

🟠 [HIGH] User-controlled task.description embedded in PR body without sanitization

📁 apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts:2293

task.description is embedded directly into the prBody string and passed to 'gh pr create --body'. A malicious description could contain null bytes, extremely long content (DoS), or malicious markdown that renders dangerously in GitHub UI. Consider using --body-file instead of --body to write to a temp file, and validate/limit description length.

Suggested fix:

1. Limit description to 65536 chars max. 2. Strip null bytes. 3. Consider using gh pr create --body-file with a temp file instead of --body to avoid any argument parsing edge cases.

🟡 [MEDIUM] No validation of targetBranch parameter from IPC

📁 apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts:2218

targetBranch is passed directly from IPC call without validation. While Git has some protections, the parameter should be validated against Git ref naming rules, checked for path traversal (no '..'), null bytes stripped, and length-limited to prevent abuse.

Suggested fix:

Add validation: if (!targetBranch || typeof targetBranch !== 'string') return error; Validate against regex /^[a-zA-Z0-9][a-zA-Z0-9._\/-]*$/ and targetBranch.length <= 255; Reject if contains '..' or null bytes.

🟡 [MEDIUM] Raw error messages may leak sensitive information

📁 apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts:2318

Error messages from git and gh CLI are returned directly to the client via stderr/error.message. This could expose: file paths, git remote URLs with embedded credentials, GitHub tokens if gh CLI prints them, and internal project structure. Error messages should be sanitized before returning.

Suggested fix:

Create sanitizeErrorMessage() that: removes ghp_ tokens, redacts file paths, removes URLs with embedded credentials, and limits message length to 500 chars. Apply to all error returns.

🟡 [MEDIUM] Path traversal risk with task.specId in file path construction

📁 apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts:2301

task.specId is used to construct file paths (specDir, planPath) without explicit validation for path traversal sequences. If specId contains '../', it could access files outside the intended directory. While projectStore may provide some validation, explicit checks should be added here.

Suggested fix:

Validate specId: if (task.specId.includes('..') || task.specId.includes('/') || task.specId.includes('\\')) return error. Also normalize and verify resolved path starts with project.path.

🟡 [MEDIUM] Non-atomic file write to implementation_plan.json

📁 apps/frontend/src/main/ipc-handlers/task/worktree-handlers.ts:2310

writeFileSync on implementation_plan.json is not atomic - if the process crashes mid-write, the file could be corrupted. The write should use a temp file + rename pattern for atomicity. Additionally, no validation of JSON structure before modification.

Suggested fix:

Write to temp file first (planPath + '.tmp'), then rename to planPath. Validate plan is an object before modifying. Wrap in try/finally to clean up temp file on failure.

🟡 [MEDIUM] handleCreatePR duplicated between Modal and Panel components

📁 apps/frontend/src/renderer/components/task-detail/TaskDetailModal.tsx:152

The handleCreatePR function is defined with identical logic in both TaskDetailModal.tsx (lines 152-177) and TaskDetailPanel.tsx (lines 115-140). This DRY violation creates maintenance burden - bug fixes must be applied in two places and logic could diverge over time.

Suggested fix:

Extract handleCreatePR to the useTaskDetail hook which is already shared between both components. Return it from the hook and call with appropriate onSuccess callback (onOpenChange(false) vs onClose).

🔵 [LOW] Nested async calls without cleanup on unmount

📁 apps/frontend/src/renderer/components/task-detail/hooks/useTaskDetail.ts:136

The useEffect for loading branches has nested async calls (getGitBranches then getProjectEnv) but no cleanup. If component unmounts during these calls, state updates will occur on unmounted component. Add isMounted flag and cleanup function to prevent memory leaks and React warnings.

Suggested fix:

Add let isMounted = true at effect start, check if (isMounted) before each setState, and return cleanup function: return () => { isMounted = false; }

🔵 [LOW] prSuccess state not cleared when task changes

📁 apps/frontend/src/renderer/components/task-detail/hooks/useTaskDetail.ts:50

The prSuccess state is set when PR creation succeeds but is never cleared when switching to a different task or when the modal is closed and reopened. This could show stale success message for wrong task.

Suggested fix:

Add useEffect that clears prSuccess when task.id changes: useEffect(() => { setPrSuccess(null); }, [task.id]);

This review was generated by Auto Claude.

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 🔄 Checking Checking PR Status feature New feature or request size/L Large (500-999 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants