Skip to content

Comments

fix: Run GitHub sync in worker thread to prevent UI blocking#2602

Closed
wwwillchen-bot wants to merge 1 commit intodyad-sh:mainfrom
wwwillchen-bot:agent-2592-1770713586
Closed

fix: Run GitHub sync in worker thread to prevent UI blocking#2602
wwwillchen-bot wants to merge 1 commit intodyad-sh:mainfrom
wwwillchen-bot:agent-2592-1770713586

Conversation

@wwwillchen-bot
Copy link
Collaborator

@wwwillchen-bot wwwillchen-bot commented Feb 10, 2026

Summary

  • Issue: Chat input was blocked while GitHub sync was in progress - commands issued during sync wouldn't appear until sync finished (Chat input blocked while GitHub sync is in progress #2592)
  • Root cause: Git operations (push/pull) were running in the main Electron process event loop, blocking IPC message processing
  • Solution: Move git operations to a dedicated worker thread that runs independently from the main process, keeping the main event loop free to handle IPC messages like chat input

Key Changes

  • Added workers/git/git_worker.ts - Worker thread that handles git push/pull/setRemoteUrl operations
  • Added src/ipc/utils/git_worker_utils.ts - Wrapper utilities for spawning and communicating with the worker
  • Added shared/git_worker_types.ts - Type-safe worker communication types
  • Updated handlePushToGithub to use worker-based git operations instead of direct calls
  • Added vite config and forge config entries for building the git worker

Technical Details

The worker uses child_process.execFile directly instead of dugite to avoid module resolution issues in packaged Electron apps. It replicates dugite's environment setup (GIT_EXEC_PATH, templates, etc.) to ensure the bundled git binary works correctly.

Test plan

  • All existing GitHub E2E tests pass (14/14)
  • All unit tests pass (784/784)
  • Manual testing: Start GitHub sync, then type in chat - input should remain responsive

Fixes #2592

🤖 Generated with Claude Code


Open with Devin

Summary by cubic

Run GitHub sync in a worker thread to keep the UI responsive during long git operations. Fixes #2592 where chat input froze until sync finished.

  • Bug Fixes
    • Offloads git push/pull/remote setup to a dedicated worker (workers/git/git_worker.ts) using execFile, with type-safe messages (shared/git_worker_types.ts) and a small wrapper (src/ipc/utils/git_worker_utils.ts).
    • Updates handlePushToGithub to call the worker for setRemoteUrl, pull, and push, so the main process can process IPC (chat input) during sync.
    • Adds build entries for the worker (vite.git-worker.config.mts, forge.config.ts) and compiles it via ts:workers; includes a worker tsconfig.
    • Mirrors dugite’s env (GIT_EXEC_PATH, templates) to use the bundled git; requires native git; sanitizes PATH on Windows to avoid WSL interop issues.

Written for commit c033417. Summary will update on new commits.

When syncing to GitHub, the chat input was blocked - commands issued during
the sync wouldn't appear until the sync finished. This was caused by git
operations blocking the main Electron process event loop.

Solution: Move git push/pull operations to a dedicated worker thread that
runs independently from the main process. This keeps the main event loop
free to handle IPC messages (like chat input) during sync.

Key changes:
- Add git_worker.ts that runs git operations in a separate thread
- Add git_worker_utils.ts wrapper for spawning the worker
- Update handlePushToGithub to use worker-based git operations
- Add vite config for building the git worker
- Add git_worker_types.ts for type-safe worker communication

The worker uses child_process.execFile directly (instead of dugite) to
avoid module resolution issues in packaged Electron apps.

Fixes dyad-sh#2592

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wwwillchen
Copy link
Collaborator

@BugBot run

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @wwwillchen-bot, 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 resolves a critical UI responsiveness issue where the application's chat input would become blocked during GitHub synchronization. The problem stemmed from git operations executing on the main Electron process's event loop, which led to UI freezes. The solution involves offloading these potentially long-running git tasks to a dedicated Node.js worker thread, thereby isolating them from the main UI thread and ensuring a consistently smooth and responsive user experience during repository interactions.

Highlights

  • Worker Thread for Git Operations: Implemented a dedicated worker thread to handle long-running GitHub synchronization operations (git push, pull, set remote URL), preventing the main UI from freezing.
  • Worker Communication Utilities: Introduced new utility functions in the main process to spawn the git worker, send commands, and receive results, ensuring type-safe communication.
  • Updated GitHub Handlers: Refactored the handlePushToGithub IPC handler to delegate git operations to the new worker-based utilities, improving UI responsiveness.
  • Build Configuration: Added necessary Vite and Electron Forge configurations to properly build and bundle the new git worker thread.
  • Windows Environment Sanitization: Included logic to sanitize Windows environment variables within the worker to avoid potential WSL interop issues when executing native git commands.
Changelog
  • forge.config.ts
    • Configured the Electron Forge build process to include the new git worker.
  • package-lock.json
    • Updated dependency metadata by adjusting 'peer' flags for various packages.
  • package.json
    • Modified the 'ts:workers' script to include TypeScript compilation for the new git worker's configuration.
  • shared/git_worker_types.ts
    • Defined type interfaces for input and output messages exchanged between the main process and the git worker.
  • src/ipc/handlers/github_handlers.ts
    • Refactored GitHub IPC handlers to delegate git push, pull, and set remote URL operations to the new worker utilities.
    • Removed direct calls to gitPush and gitPull.
  • src/ipc/utils/git_worker_utils.ts
    • Added utility functions for spawning and communicating with the git worker thread.
    • Implemented logic to sanitize Windows environment variables for git commands within the worker.
  • vite.git-worker.config.mts
    • Created a new Vite configuration file specifically for bundling the git worker script.
  • workers/git/git_worker.ts
    • Implemented the core logic for the git worker, executing git commands via child_process.execFile.
    • Handled communication of git operation results back to the main process.
  • workers/git/tsconfig.json
    • Added a new TypeScript configuration file for the git worker.
Activity
  • No specific activity (comments, reviews, or progress updates) was provided in the context.
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.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 10, 2026

Greptile Overview

Greptile Summary

This PR moves GitHub sync (push/pull/remote URL updates) out of the Electron main process event loop into a dedicated Node worker thread, aiming to keep IPC (e.g., chat input) responsive during long-running git operations. It introduces a new git worker entrypoint (workers/git/git_worker.ts), shared message types, a main-process wrapper (src/ipc/utils/git_worker_utils.ts), and build wiring via Forge/Vite.

The overall direction fits the existing pattern used for the TypeScript worker (src/ipc/processors/tsc.ts), but there are a few correctness/integration issues to address before merge (worker script path resolution, concurrency locking around repo mutation, and exit-code handling in the worker).

Confidence Score: 2/5

  • Not safe to merge until worker wiring and error handling are fixed.
  • There are at least two merge-blocking runtime issues: the main process tries to spawn git_worker.js relative to src/ipc/utils/ (likely not where the built worker is emitted), and the worker’s exitCode typing/handling can be incorrect (string codes) which breaks error checks. Additionally, push/pull operations are no longer protected by the existing per-repo lock, enabling concurrent mutations.
  • src/ipc/utils/git_worker_utils.ts, workers/git/git_worker.ts, src/ipc/handlers/github_handlers.ts

Important Files Changed

Filename Overview
forge.config.ts Adds Vite build entry for the new git worker; no functional issues in config itself.
package-lock.json Lockfile updates from dependency resolution; no direct code issues to review.
package.json Extends ts:workers script to typecheck the new git worker tsconfig; straightforward change.
shared/git_worker_types.ts Introduces shared typed message contracts for git worker input/output. Straightforward union types.
src/ipc/handlers/github_handlers.ts Switches GitHub push flow to use worker-based git operations. Introduces a concurrency risk because repo operations are not wrapped in withLock.
src/ipc/utils/git_worker_utils.ts Adds worker spawning wrapper for git operations. Likely broken at runtime due to __dirname lookup for git_worker.js not matching where the built worker file is emitted.
vite.git-worker.config.mts Adds a dedicated Vite build config for the git worker (CJS lib build). Build config seems fine, but integration depends on loading the emitted artifact at runtime.
workers/git/git_worker.ts Implements git operations via execFile in a worker thread with dugite-like env setup. Bug: exitCode can become a string (e.g., ENOENT), breaking error checks/typing.
workers/git/tsconfig.json Adds tsconfig for git worker compilation/typechecking. No runtime logic.

Sequence Diagram

sequenceDiagram
  participant R as Renderer
  participant M as Main (github_handlers)
  participant W as Git Worker Thread
  participant G as git (execFile)

  R->>M: IPC invoke github:push {appId, force?}
  M->>M: readSettings() / db lookup app
  M->>W: runGitOperation(setRemoteUrl)
  W->>G: git remote set-url/add origin <remoteUrl>
  G-->>W: exit code + stderr/stdout
  W-->>M: {success|error}

  alt not force push
    M->>W: runGitOperation(pull)
    W->>G: git pull origin <branch>
    G-->>W: exit code + stderr/stdout
    W-->>M: {success|error,name?}
    M->>M: map conflict errors to GitConflictError
  end

  M->>W: runGitOperation(push)
  W->>G: git push origin <branch>:<branch> [--force*]
  G-->>W: exit code + stderr/stdout
  W-->>M: {success|error}
  M-->>R: IPC resolve/reject
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

9 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +70 to +72
const workerPath = path.join(__dirname, "git_worker.js");

logger.info(`Starting git worker for operation: ${input.type}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

Worker path likely wrong

runGitOperation constructs the worker script path as path.join(__dirname, "git_worker.js"), but the compiled/bundled entry for this worker comes from workers/git/git_worker.ts (built via vite.git-worker.config.mts). Unless the build output is also copied into src/ipc/utils/ at runtime (which doesn’t appear to be the case), this will fail at runtime with “cannot find module …/git_worker.js”. The existing TSC worker (src/ipc/processors/tsc.ts:26) works because tsc_worker.js is co-located with that module; the git worker isn’t.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ipc/utils/git_worker_utils.ts
Line: 70:72

Comment:
**Worker path likely wrong**

`runGitOperation` constructs the worker script path as `path.join(__dirname, "git_worker.js")`, but the compiled/bundled entry for this worker comes from `workers/git/git_worker.ts` (built via `vite.git-worker.config.mts`). Unless the build output is also copied into `src/ipc/utils/` at runtime (which doesn’t appear to be the case), this will fail at runtime with “cannot find module …/git_worker.js”. The existing TSC worker (`src/ipc/processors/tsc.ts:26`) works because `tsc_worker.js` is co-located with that module; the git worker isn’t.


How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +165 to +168
const result = await execGit(args, appPath, sanitizedEnv);
if (result.exitCode !== 0) {
const errorMsg = result.stderr || result.stdout;
return { success: false, error: `Git push failed: ${errorMsg}` };
Copy link
Contributor

Choose a reason for hiding this comment

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

Pull conflict detection broken

execGit always returns { exitCode: 0 } in the success path, but in the error path it sets exitCode: error.code || 1. For execFile, error.code is often a string like "ENOENT", so exitCode can become a string. In handlePull/handlePush you compare result.exitCode !== 0, which will be true for every successful command that returns exitCode: 0? (fine) but also for string codes in failures; more importantly, the type mismatch means conflict detection/error handling becomes inconsistent and can misclassify failures. This needs to ensure exitCode is always numeric (and keep the original error code separately if needed).

Prompt To Fix With AI
This is a comment left during a code review.
Path: workers/git/git_worker.ts
Line: 165:168

Comment:
**Pull conflict detection broken**

`execGit` always returns `{ exitCode: 0 }` in the success path, but in the error path it sets `exitCode: error.code || 1`. For `execFile`, `error.code` is often a string like `"ENOENT"`, so `exitCode` can become a string. In `handlePull`/`handlePush` you compare `result.exitCode !== 0`, which will be `true` for *every successful command that returns `exitCode: 0`?* (fine) but also for string codes in failures; more importantly, the type mismatch means conflict detection/error handling becomes inconsistent and can misclassify failures. This needs to ensure `exitCode` is always numeric (and keep the original error code separately if needed).


How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +863 to 866
await gitSetRemoteUrlWorker({
appPath,
remoteUrl,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing repo lock around git ops

handlePushToGithub used to run git operations in-process; after moving to the worker, the gitPullWorker/gitPushWorker calls are no longer wrapped in withLock(appId, ...) (unlike other operations such as prepareLocalBranch). This allows concurrent sync/push operations for the same appId to overlap and mutate the same repo simultaneously, which will produce nondeterministic failures (e.g., index.lock errors, partial pulls, or push after a conflicting pull). Wrap the pull/push (and remote URL update) in the same per-app lock.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ipc/handlers/github_handlers.ts
Line: 863:866

Comment:
**Missing repo lock around git ops**

`handlePushToGithub` used to run git operations in-process; after moving to the worker, the `gitPullWorker`/`gitPushWorker` calls are no longer wrapped in `withLock(appId, ...)` (unlike other operations such as `prepareLocalBranch`). This allows concurrent sync/push operations for the same `appId` to overlap and mutate the same repo simultaneously, which will produce nondeterministic failures (e.g., index.lock errors, partial pulls, or push after a conflicting pull). Wrap the pull/push (and remote URL update) in the same per-app lock.


How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +192 to +193
const args = ["pull", remote, branch];
const result = await execGit(args, appPath, sanitizedEnv);
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Worker git pull missing --rebase=false, author config, and credential.helper= flags present in the original implementation

The worker's handlePull constructs the git pull command as ["pull", remote, branch], but the original gitPull in src/ipc/utils/git_utils.ts:1174-1181 uses a much more complete command:

["-c", "user.name=...", "-c", "user.email=...", "-c", "credential.helper=", "pull", "--rebase=false", remote, branch]
Missing flags and their impact
  1. --rebase=false: Without this flag, if the user has pull.rebase=true in their global or local git config, the pull will rebase instead of merge. The original code explicitly forces merge behavior. This changes the git history shape and can lead to unexpected conflicts or behavior.

  2. -c user.name=... -c user.email=... (via withGitAuthor): The original code sets the committer/author identity for merge commits created during pull. Without this, merge commits will use whatever is in the user's global git config, or fail/use empty identity if none is configured. Since this runs in a worker thread with a copied process.env, the git author must be explicitly set.

  3. -c credential.helper=: The original code disables credential helpers to prevent git from launching interactive prompts. In a worker thread, an interactive credential prompt would silently hang the operation indefinitely since there's no terminal attached.

Impact: Any git pull that creates a merge commit will have incorrect author info. Users with pull.rebase=true will get rebases instead of merges. Credential helper invocations could hang the worker.

Prompt for agents
In workers/git/git_worker.ts, the handlePull function at line 192 needs to replicate the flags used by the original gitPull in src/ipc/utils/git_utils.ts lines 1174-1181. Specifically:

1. Add --rebase=false to force merge strategy.
2. Add -c credential.helper= to disable credential helpers (which would hang the worker).
3. Add -c user.name=<name> -c user.email=<email> to set the author identity for any merge commits.

Since the worker can't call withGitAuthor (it relies on getGitAuthor which reads settings), you'll need to either:
- Pass the git author name/email as part of the GitPullWorkerInput type (add fields to shared/git_worker_types.ts), populate them in gitPullWorker in src/ipc/utils/git_worker_utils.ts from getGitAuthor(), and use them in the worker.
- Or find another way to make the author info available in the worker.

The final args should look like: ["-c", "user.name=<name>", "-c", "user.email=<email>", "-c", "credential.helper=", "pull", "--rebase=false", remote, branch].
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +98 to +103
worker.on("exit", (code) => {
if (code !== 0) {
logger.error(`Git worker exited with code ${code}`);
reject(new Error(`Worker exited with code ${code}`));
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 worker.terminate() in message handler causes spurious error log and reject on every successful operation

In runGitOperation, when the message event fires, worker.terminate() is called at line 83 before resolving the promise. worker.terminate() causes the worker to exit with code 1, which triggers the exit event handler at line 98-103. Since code is 1 (not 0), the exit handler logs Git worker exited with code 1 as an error and calls reject().

Detailed explanation of the race

The sequence on every successful git operation is:

  1. Worker sends message → message handler fires
  2. worker.terminate() is called at line 83
  3. resolve(output) is called at line 89 — promise is settled
  4. Worker exits with code 1 (due to terminate()) → exit handler fires
  5. logger.error("Git worker exited with code 1") logs a spurious error at line 100
  6. reject(new Error(...)) at line 101 is a no-op since promise is already resolved

Impact: Every successful git worker operation produces a misleading error log entry (Git worker exited with code 1), which pollutes the logs and makes real errors harder to find.

Suggested change
worker.on("exit", (code) => {
if (code !== 0) {
logger.error(`Git worker exited with code ${code}`);
reject(new Error(`Worker exited with code ${code}`));
}
});
let settled = false;
worker.on("message", (output: GitWorkerOutput) => {
settled = true;
worker.terminate();
if (output.success) {
logger.info(`Git worker completed successfully: ${input.type}`);
} else {
logger.error(`Git worker failed: ${input.type} - ${output.error}`);
}
resolve(output);
});
worker.on("error", (error) => {
logger.error(`Git worker error: ${input.type}`, error);
settled = true;
worker.terminate();
reject(error);
});
worker.on("exit", (code) => {
if (code !== 0 && !settled) {
logger.error(`Git worker exited with code ${code}`);
reject(new Error(`Worker exited with code ${code}`));
}
});
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions
Copy link
Contributor

🔍 Dyadbot Code Review Summary

Found 6 issue(s) flagged by consensus of 3 independent reviewers.

Summary

Severity Count
🔴 HIGH 2
🟡 MEDIUM 4

Issues to Address

Severity File Issue
🔴 HIGH workers/git/git_worker.ts:192 Worker pull missing --rebase=false, -c credential.helper=, and git author config
🔴 HIGH src/ipc/utils/git_worker_utils.ts:82 Promise double-settlement race between message/exit handlers
🟡 MEDIUM workers/git/git_worker.ts:128 error.code may be a string, not a numeric exit code
🟡 MEDIUM shared/git_worker_types.ts:10 accessToken field accepted in types but silently ignored by worker
🟡 MEDIUM src/ipc/handlers/github_handlers.ts:973 Inconsistent use of worker vs direct call for gitSetRemoteUrl in rebase handler
🟡 MEDIUM workers/git/git_worker.ts:120 maxBuffer of 100MB is excessive for push/pull operations

See inline comments for details.

Generated by Dyadbot multi-agent code review (3 agents, consensus voting)

};
}

const args = ["pull", remote, branch];
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 HIGH: Worker pull missing --rebase=false, -c credential.helper=, and git author config

The original gitPull in git_utils.ts (line 1174) uses withGitAuthor and passes -c credential.helper= and --rebase=false. This worker's handlePull uses a bare ["pull", remote, branch] with none of these flags, causing three behavioral regressions:

  1. Missing --rebase=false: If the user's global git config has pull.rebase=true, the pull will rebase instead of merge. This could produce unexpected history rewrites and break the conflict detection logic (which looks for merge-conflict keywords).

  2. Missing -c credential.helper=: Without explicitly disabling credential helpers, git may invoke an interactive credential prompt which will hang the worker thread indefinitely since there's no TTY.

  3. Missing git author config (-c user.name=... -c user.email=...): If a pull results in a merge commit, git needs author/committer identity. Without it, the operation will fail with "Author identity unknown" if the user hasn't configured git globally.

Suggested change
const args = ["pull", remote, branch];
const args = ["-c", "credential.helper=", "pull", "--rebase=false", remote, branch];

Note: The git author config (-c user.name=... -c user.email=...) also needs to be added. This requires either passing author info through the worker input message or reading it from git config inside the worker, similar to what withGitAuthor does in git_utils.ts.

(Flagged by 3/3 reviewers)

Comment on lines +82 to +103
worker.on("message", (output: GitWorkerOutput) => {
worker.terminate();
if (output.success) {
logger.info(`Git worker completed successfully: ${input.type}`);
} else {
logger.error(`Git worker failed: ${input.type} - ${output.error}`);
}
resolve(output);
});

worker.on("error", (error) => {
logger.error(`Git worker error: ${input.type}`, error);
worker.terminate();
reject(error);
});

worker.on("exit", (code) => {
if (code !== 0) {
logger.error(`Git worker exited with code ${code}`);
reject(new Error(`Worker exited with code ${code}`));
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 HIGH: Promise double-settlement race between message/exit handlers

When the message handler fires, it calls worker.terminate() then resolve(). The terminate() call triggers the exit event with a non-zero code (typically 1), which then calls reject(). This causes:

  1. Spurious error logs on every successful operation: "Git worker exited with code 1" is logged even when the operation succeeded.
  2. Potential unhandled rejection: If error fires and then exit fires (both before message), reject is called twice.
  3. No timeout: If the worker crashes before sending a message, the Promise will never resolve.

Fix: Add a settled flag to guard all resolve/reject calls:

Suggested change
worker.on("message", (output: GitWorkerOutput) => {
worker.terminate();
if (output.success) {
logger.info(`Git worker completed successfully: ${input.type}`);
} else {
logger.error(`Git worker failed: ${input.type} - ${output.error}`);
}
resolve(output);
});
worker.on("error", (error) => {
logger.error(`Git worker error: ${input.type}`, error);
worker.terminate();
reject(error);
});
worker.on("exit", (code) => {
if (code !== 0) {
logger.error(`Git worker exited with code ${code}`);
reject(new Error(`Worker exited with code ${code}`));
}
});
let settled = false;
worker.on("message", (output: GitWorkerOutput) => {
if (settled) return;
settled = true;
worker.terminate();
if (output.success) {
logger.info(`Git worker completed successfully: ${input.type}`);
} else {
logger.error(`Git worker failed: ${input.type} - ${output.error}`);
}
resolve(output);
});
worker.on("error", (error) => {
if (settled) return;
settled = true;
logger.error(`Git worker error: ${input.type}`, error);
worker.terminate();
reject(error);
});
worker.on("exit", (code) => {
if (settled) return;
if (code !== 0) {
settled = true;
logger.error(`Git worker exited with code ${code}`);
reject(new Error(`Worker exited with code ${code}`));
}
});

(Flagged by 3/3 reviewers)

return {
stdout: error.stdout || "",
stderr: error.stderr || error.message || "",
exitCode: error.code || 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM: error.code may be a string, not a numeric exit code

When Node's child_process.execFile throws, error.code can be a string like 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER' or 'ENOENT' (when git binary is not found), not a numeric exit code. The actual numeric exit code is available on error.status.

Using error.code means exitCode could be set to a string, which would still pass the !== 0 check (strings are truthy), but the ExecResult.exitCode type declares number. Downstream callers in github_handlers.ts also check pullError?.code === "MissingRefError" / "NotFoundError" which are isomorphic-git codes the worker will never produce.

Suggested change
exitCode: error.code || 1,
exitCode: typeof error.code === 'number' ? error.code : (error.status ?? 1),

(Flagged by 3/3 reviewers)

type: "push";
appPath: string;
branch: string;
accessToken?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM: accessToken field accepted in types but silently ignored by the worker

Both GitPushWorkerInput.accessToken and GitPullWorkerInput.accessToken are defined here and the main-process wrappers pass them through. However, the worker (git_worker.ts) never reads or uses accessToken in any handler -- the destructuring simply ignores it.

Authentication works because the token is already embedded in the remote URL (set via gitSetRemoteUrlWorker). However, having an unused accessToken field is misleading -- it suggests the token is used for auth in the worker, which it isn't. If someone removes the token from the URL thinking "it's passed separately," pull/push would fail silently.

Consider either removing accessToken from the worker types entirely, or adding a comment explaining it's present for API compatibility but unused since auth is via the remote URL.

(Flagged by 3/3 reviewers)

const { stdout, stderr } = await execFileAsync(GIT_PATH, args, {
cwd,
env,
maxBuffer: 100 * 1024 * 1024, // 100MB
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM: maxBuffer of 100MB is excessive for push/pull operations

For git push/pull/remote operations, stdout/stderr should be small (a few KB at most). A 100MB buffer means if git produces unexpectedly large output (e.g., a corrupted repository), the worker will buffer up to 100MB in memory. Since a new worker is spawned per operation, concurrent operations could consume significant memory.

A more reasonable buffer like 10MB would be sufficient:

Suggested change
maxBuffer: 100 * 1024 * 1024, // 100MB
maxBuffer: 10 * 1024 * 1024, // 10MB

(Flagged by 2/3 reviewers)

@github-actions
Copy link
Contributor

🟡 MEDIUM: Inconsistent use of worker vs direct call for gitSetRemoteUrl in rebase handler

handlePushToGithub (line 863) uses the new gitSetRemoteUrlWorker (worker-based), but handleRebaseFromGithub (line 973) still uses the old blocking gitSetRemoteUrl directly. Both are called from IPC handlers on the main process.

While gitSetRemoteUrl for a local config change is fast and unlikely to block noticeably, this inconsistency could confuse future maintainers about which approach should be used. Consider either migrating the rebase handler to use the worker too, or adding a comment explaining why it's intentionally left as a direct call.

(Flagged by 2/3 reviewers)

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 9 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="workers/git/git_worker.ts">

<violation number="1" location="workers/git/git_worker.ts:128">
P2: `error.code` can be a string (e.g., `'ENOENT'`) when the process fails to spawn, causing a runtime type mismatch with the `exitCode: number` field. Use a type guard to ensure a numeric exit code.</violation>

<violation number="2" location="workers/git/git_worker.ts:192">
P1: The git pull command is missing critical flags from the original implementation: `--rebase=false` (to force merge behavior regardless of user's git config), and `-c credential.helper=` (to prevent credential helper prompts from hanging the worker). Without `--rebase=false`, users with `pull.rebase=true` in their git config will get rebases instead of merges, changing the expected git history.</violation>
</file>

<file name="src/ipc/handlers/github_handlers.ts">

<violation number="1" location="src/ipc/handlers/github_handlers.ts:862">
P1: The git worker operations (pull, push, setRemoteUrl) are not wrapped in a per-app lock. If multiple sync operations run concurrently for the same app, they could produce index.lock errors, partial pulls, or race conditions. The original implementation likely used `withLock(appId, ...)` to serialize these operations. Consider wrapping these calls in a lock to prevent concurrent repo mutations.</violation>
</file>

<file name="src/ipc/utils/git_worker_utils.ts">

<violation number="1" location="src/ipc/utils/git_worker_utils.ts:98">
P2: Bug: `worker.terminate()` causes exit with code 1, so the `exit` handler will log a spurious error and attempt to reject an already-settled promise on every successful operation. Add a `settled` flag to guard against this race condition between the `message`/`error` handlers and the `exit` handler.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

};
}

const args = ["pull", remote, branch];
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

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

P1: The git pull command is missing critical flags from the original implementation: --rebase=false (to force merge behavior regardless of user's git config), and -c credential.helper= (to prevent credential helper prompts from hanging the worker). Without --rebase=false, users with pull.rebase=true in their git config will get rebases instead of merges, changing the expected git history.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At workers/git/git_worker.ts, line 192:

<comment>The git pull command is missing critical flags from the original implementation: `--rebase=false` (to force merge behavior regardless of user's git config), and `-c credential.helper=` (to prevent credential helper prompts from hanging the worker). Without `--rebase=false`, users with `pull.rebase=true` in their git config will get rebases instead of merges, changing the expected git history.</comment>

<file context>
@@ -0,0 +1,288 @@
+      };
+    }
+
+    const args = ["pull", remote, branch];
+    const result = await execGit(args, appPath, sanitizedEnv);
+    if (result.exitCode !== 0) {
</file context>
Fix with Cubic

await gitSetRemoteUrl({
path: appPath,

// Set or update remote URL using git config (in worker thread)
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

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

P1: The git worker operations (pull, push, setRemoteUrl) are not wrapped in a per-app lock. If multiple sync operations run concurrently for the same app, they could produce index.lock errors, partial pulls, or race conditions. The original implementation likely used withLock(appId, ...) to serialize these operations. Consider wrapping these calls in a lock to prevent concurrent repo mutations.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/ipc/handlers/github_handlers.ts, line 862:

<comment>The git worker operations (pull, push, setRemoteUrl) are not wrapped in a per-app lock. If multiple sync operations run concurrently for the same app, they could produce index.lock errors, partial pulls, or race conditions. The original implementation likely used `withLock(appId, ...)` to serialize these operations. Consider wrapping these calls in a lock to prevent concurrent repo mutations.</comment>

<file context>
@@ -852,17 +858,18 @@ async function handlePushToGithub(
-  await gitSetRemoteUrl({
-    path: appPath,
+
+  // Set or update remote URL using git config (in worker thread)
+  await gitSetRemoteUrlWorker({
+    appPath,
</file context>
Fix with Cubic

reject(error);
});

worker.on("exit", (code) => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

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

P2: Bug: worker.terminate() causes exit with code 1, so the exit handler will log a spurious error and attempt to reject an already-settled promise on every successful operation. Add a settled flag to guard against this race condition between the message/error handlers and the exit handler.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/ipc/utils/git_worker_utils.ts, line 98:

<comment>Bug: `worker.terminate()` causes exit with code 1, so the `exit` handler will log a spurious error and attempt to reject an already-settled promise on every successful operation. Add a `settled` flag to guard against this race condition between the `message`/`error` handlers and the `exit` handler.</comment>

<file context>
@@ -0,0 +1,209 @@
+      reject(error);
+    });
+
+    worker.on("exit", (code) => {
+      if (code !== 0) {
+        logger.error(`Git worker exited with code ${code}`);
</file context>
Fix with Cubic

return {
stdout: error.stdout || "",
stderr: error.stderr || error.message || "",
exitCode: error.code || 1,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

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

P2: error.code can be a string (e.g., 'ENOENT') when the process fails to spawn, causing a runtime type mismatch with the exitCode: number field. Use a type guard to ensure a numeric exit code.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At workers/git/git_worker.ts, line 128:

<comment>`error.code` can be a string (e.g., `'ENOENT'`) when the process fails to spawn, causing a runtime type mismatch with the `exitCode: number` field. Use a type guard to ensure a numeric exit code.</comment>

<file context>
@@ -0,0 +1,288 @@
+    return {
+      stdout: error.stdout || "",
+      stderr: error.stderr || error.message || "",
+      exitCode: error.code || 1,
+    };
+  }
</file context>
Fix with Cubic

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses the UI blocking issue during GitHub sync operations by moving them to a dedicated worker thread. The implementation is solid, with suggestions provided to improve robustness and maintainability through reduced code duplication and refined error handling. From a security perspective, while no vulnerabilities were found in the processed files, several empty files prevented a complete SAST Reconnaissance scan.

Comment on lines +244 to +258
if (setResult.exitCode !== 0) {
// Remote might not exist, try adding it
const addResult = await execGit(
["remote", "add", "origin", remoteUrl],
appPath,
sanitizedEnv,
);

if (addResult.exitCode !== 0) {
return {
success: false,
error: `Failed to set remote URL: ${addResult.stderr || addResult.stdout}`,
};
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The current implementation attempts to git remote add if git remote set-url fails for any reason. This could mask the original error. For example, if set-url fails due to an invalid URL, the code will then try to add the remote, which might also fail (e.g., if the remote already exists), leading to a confusing error message.

It would be more robust to specifically check if set-url failed because the remote does not exist before attempting to add it.

    if (setResult.exitCode !== 0) {
      // If `set-url` failed because the remote doesn't exist, try adding it.
      if (setResult.stderr.includes("No such remote")) {
        const addResult = await execGit(
          ["remote", "add", "origin", remoteUrl],
          appPath,
          sanitizedEnv,
        );

        if (addResult.exitCode !== 0) {
          return {
            success: false,
            error: `Failed to add remote 'origin': ${addResult.stderr || addResult.stdout}`,
          };
        }
      } else {
        // `set-url` failed for a different reason.
        return {
          success: false,
          error: `Failed to set remote URL: ${setResult.stderr || setResult.stdout}`,
        };
      }
    }

Comment on lines +138 to +147
if (!result.success) {
const error = new Error(result.error);
if (result.name) {
(error as any).name = result.name;
}
if (result.code) {
(error as any).code = result.code;
}
throw error;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic for checking the worker result and re-throwing a structured error is duplicated in gitPushWorker, gitPullWorker, and gitSetRemoteUrlWorker. This can be extracted into a helper function to avoid repetition and make the code more maintainable.

For example, you could add a helper function:

function handleWorkerResult(result: GitWorkerOutput): void {
  if (!result.success) {
    const error = new Error(result.error);
    if (result.name) {
      (error as any).name = result.name;
    }
    if (result.code) {
      (error as any).code = result.code;
    }
    throw error;
  }
}

Then you can simplify this function to just handleWorkerResult(result);.

Comment on lines +269 to +282
async function processGitOperation(
input: GitWorkerInput,
): Promise<GitWorkerOutput> {
switch (input.type) {
case "push":
return handlePush(input);
case "pull":
return handlePull(input);
case "setRemoteUrl":
return handleSetRemoteUrl(input);
default:
return { success: false, error: `Unknown operation type` };
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The check for enableNativeGit is repeated in handlePush, handlePull, and handleSetRemoteUrl. To reduce code duplication and improve maintainability, this check can be moved to the processGitOperation function, as it's a common precondition for all operations handled by this worker.

async function processGitOperation(
  input: GitWorkerInput,
): Promise<GitWorkerOutput> {
  if (!input.enableNativeGit) {
    return {
      success: false,
      error:
        'Git worker requires native git to be enabled. Please enable native git in settings.',
    };
  }

  switch (input.type) {
    case "push":
      return handlePush(input);
    case "pull":
      return handlePull(input);
    case "setRemoteUrl":
      return handleSetRemoteUrl(input);
    default:
      return { success: false, error: `Unknown operation type` };
  }
}

@github-actions github-actions bot added cc:pending cc:done needs-human:review-issue ai agent flagged an issue that requires human review and removed cc:request cc:pending labels Feb 10, 2026
@wwwillchen wwwillchen marked this pull request as draft February 11, 2026 07:51
@wwwillchen wwwillchen closed this Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cc:done needs-human:review-issue ai agent flagged an issue that requires human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chat input blocked while GitHub sync is in progress

2 participants