Skip to content

Prevent concurrent file copying#2841

Merged
wwwillchen merged 2 commits intodyad-sh:mainfrom
azizmejri1:prevent-concurrent-copy
Mar 2, 2026
Merged

Prevent concurrent file copying#2841
wwwillchen merged 2 commits intodyad-sh:mainfrom
azizmejri1:prevent-concurrent-copy

Conversation

@azizmejri1
Copy link
Copy Markdown
Collaborator

@azizmejri1 azizmejri1 commented Mar 1, 2026

closes #2804


Open with Devin

@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 addresses potential race conditions during file copy operations by introducing a locking mechanism. By wrapping the file copying logic within a lock, it ensures that only one file copy process can execute at a time, thereby enhancing the stability and integrity of file system manipulations within the application.

Highlights

  • Concurrency Control: Implemented a locking mechanism to prevent concurrent execution of the executeCopyFile function, ensuring that file copy operations are serialized.
  • New Utility Import: Introduced an import for withLock from a new lock_utils module to facilitate the concurrency control.
Changelog
  • src/ipc/utils/copy_file_utils.ts
    • Imported the withLock utility function.
    • Wrapped the executeCopyFile function with withLock to prevent concurrent execution.
Activity
  • The pull request was opened by azizmejri1.
  • No human activity (comments, reviews) has been recorded yet.
Using Gemini Code Assist

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

Invoking Gemini

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

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

Customization

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

Limitations & Feedback

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

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

Footnotes

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

Copy link
Copy Markdown
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.

No issues found across 1 file

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Copy link
Copy Markdown
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: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

chatgpt-codex-connector[bot]

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 1, 2026

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE - Potential issues

Reviewed by 3 independent agents: Correctness Expert, Code Health Expert, UX Wizard.

Issues Summary

Severity File Issue
πŸ”΄ HIGH src/ipc/utils/lock_utils.ts (pre-existing) withLock race condition with 3+ concurrent callers defeats mutual exclusion
🟑 MEDIUM src/ipc/utils/copy_file_utils.ts:47 Lock key uses appPath string instead of numeric appId, inconsistent with all other lock usage

Details

πŸ”΄ withLock race condition (pre-existing in lock_utils.ts)

The withLock implementation has a TOCTOU race when 3+ callers contend on the same lockId. All waiting callers await the same promise. When it resolves, they all resume and call acquireLock simultaneously β€” each overwriting the map entry. This means multiple callers execute the critical section concurrently, defeating the lock's purpose.

Example scenario:

  1. Copy A holds the lock, copies B and C both await existingLock
  2. A finishes and releases β†’ both B and C resume
  3. B calls acquireLock, C calls acquireLock (overwrites B's entry)
  4. B and C now execute concurrently β€” race condition

This is not introduced by this PR but limits its effectiveness. A proper fix would use a promise-chain pattern where each caller chains onto the tail of the queue.

🟑 Lock key inconsistency

All other withLock callers in the codebase (app_handlers.ts, git_branch_handlers.ts, version_handlers.ts, github_handlers.ts) use numeric appId. This PR uses appPath (a string), creating an isolated lock domain. Concurrent copy + git operations (branch switch, version restore, commit) on the same app will NOT be mutually exclusive, potentially causing git index corruption or partially staged files.

🚫 Dropped False Positives (4 items)
  • acquireLock silently overwrites existing lock entry - Dropped: Same root cause as the withLock race condition above, not a separate issue
  • Lock map entry deleted before promise resolves - Dropped: Theoretical concern only; delete and resolve are synchronous in the Node.js event loop, so no interleaving occurs in practice
  • acquireLock exported but only used internally - Dropped: Pre-existing code not part of this diff; out of scope for this review
  • No user feedback when copy operation is queued - Dropped: Valid UX concern but out of scope for this PR's intent

Generated by Dyadbot multi-agent code review

github-actions[bot]

This comment was marked as resolved.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 1, 2026

Greptile Summary

This PR successfully prevents concurrent file copying operations by implementing a per-app locking mechanism and adding symlink traversal security checks.

Key Changes:

  • Rewrote withLock in lock_utils.ts to use promise-chaining instead of the previous await-then-acquire pattern, fixing the race condition where multiple waiting operations could acquire locks simultaneously
  • Added appId parameter to executeCopyFile and wrapped the entire function body in withLock(appId, ...) to serialize file copy operations per app
  • Added symlink traversal protection using fs.realpathSync() to validate that resolved paths stay within allowed directories
  • Changed lock key from appPath (string) to appId (number) to share the lock domain with other app-level operations

The promise-chaining implementation correctly ensures that queued operations execute serially by chaining each new operation onto the previous one's completion promise.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation correctly addresses the concurrency race condition using a well-structured promise-chaining approach, adds important security protections against symlink traversal, and updates all call sites consistently. The code logic has been thoroughly validated and no issues were found.
  • No files require special attention

Important Files Changed

Filename Overview
src/ipc/utils/lock_utils.ts Completely rewrote locking mechanism to use promise-chaining, fixing the race condition where multiple waiters could acquire locks simultaneously
src/ipc/utils/copy_file_utils.ts Added appId parameter and wrapped function in withLock, plus symlink traversal security checks using fs.realpathSync

Sequence Diagram

sequenceDiagram
    participant Caller1 as Caller 1
    participant Caller2 as Caller 2
    participant ExecuteCopy as executeCopyFile
    participant WithLock as withLock
    participant FileSystem as File System

    Caller1->>ExecuteCopy: executeCopyFile(appId: 1, from, to)
    ExecuteCopy->>WithLock: withLock(1, copyOperation)
    Note over WithLock: No existing lock<br/>Creates newLock1<br/>locks.set(1, newLock1)
    WithLock-->>ExecuteCopy: Promise chained to resolved promise
    
    Caller2->>ExecuteCopy: executeCopyFile(appId: 1, from2, to2)
    ExecuteCopy->>WithLock: withLock(1, copyOperation2)
    Note over WithLock: Existing lock found (newLock1)<br/>Creates newLock2<br/>locks.set(1, newLock2)<br/>Chains to newLock1
    WithLock-->>ExecuteCopy: Promise chained to newLock1
    
    Note over ExecuteCopy,FileSystem: Operation 1 executes
    ExecuteCopy->>FileSystem: Validate paths, copy file
    FileSystem-->>ExecuteCopy: Success
    Note over WithLock: resolve() called<br/>newLock1 resolves<br/>Lock not deleted (newLock2 exists)
    
    Note over ExecuteCopy,FileSystem: Operation 2 executes (after 1 completes)
    ExecuteCopy->>FileSystem: Validate paths, copy file
    FileSystem-->>ExecuteCopy: Success
    Note over WithLock: resolve() called<br/>newLock2 resolves<br/>Lock deleted (no newer lock)
    
    ExecuteCopy-->>Caller1: Success
    ExecuteCopy-->>Caller2: Success
Loading

Last reviewed commit: 03a2e82

greptile-apps[bot]

This comment was marked as resolved.

@github-actions github-actions bot added the needs-human:review-issue ai agent flagged an issue that requires human review label Mar 1, 2026
- Fix withLock race condition by using promise-chaining instead of await-then-acquire pattern
- Use numeric appId instead of appPath string for lock key to share lock domain with other app operations
- Add symlink traversal protection using fs.realpathSync to prevent escaping allowed directories

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

@BugBot run

@azizmejri1
Copy link
Copy Markdown
Collaborator Author

πŸ€– Claude Code Review Summary

PR Confidence: 4/5

All review comments have been addressed with code changes; the lock race condition fix, lock key alignment, and symlink protection are solid but would benefit from integration testing.

Unresolved Threads

No unresolved threads

Resolved Threads

Issue Rationale Link
withLock race condition (3 threads) Rewrote lock_utils.ts to use promise-chaining so queued operations execute serially instead of all acquiring simultaneously after release View 1, View 2, View 3
Lock key uses appPath instead of numeric appId Changed executeCopyFile to accept and use numeric appId for withLock, aligning with the rest of the codebase so copy operations share the same lock domain as git/version operations View
Symlink traversal vulnerability Added fs.realpathSync validation after path resolution to ensure source paths don't escape allowed directories via symlinks View
Product Principle Suggestions

No suggestions β€” product principles were not needed for these technical/security review comments.


πŸ€– Generated by Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

πŸ” Dyadbot Code Review Summary

Verdict: βœ… YES - Ready to merge

Reviewed by 3 independent agents: Correctness Expert, Code Health Expert, UX Wizard.

The core concurrency fix (promise-chaining in lock_utils.ts, using appId as lock key) is correct and well-implemented. The lock correctly stores newLock (always resolved in finally) rather than result (which can reject), so no deadlock is possible despite what some bot reviewers flagged. Symlink hardening on the source path is a good security improvement.

Issues Summary

Severity File Issue
🟑 MEDIUM src/ipc/utils/copy_file_utils.ts:100 Misleading comment + missing destination symlink validation
🟒 Low Priority Notes (3 items)
  • Dead code in symlink check - src/ipc/utils/copy_file_utils.ts:86 - The realFromPath === resolvedAppPath condition is unreachable (a file path can never equal a directory path)
  • Lock scope includes Supabase deploy - src/ipc/utils/copy_file_utils.ts:49-133 - The withLock wraps deploySupabaseFunction which could block other appId operations during slow deploys. The gitAdd must stay locked, but deploy could theoretically be moved out
  • Technical error messages - src/ipc/utils/copy_file_utils.ts:79-91 - "possible symlink traversal" is jargon that could confuse end users if surfaced in the UI
🚫 Dropped False Positives (5 items)
  • Permanent deadlock after rejected operation (raised by 2 agents) - Dropped: The locks map stores newLock (always resolved in finally block), NOT result (which can reject). Each newLock is resolved via resolve() regardless of whether fn() succeeds or throws. The chain is correct β€” lastOperation is always a resolved promise.
  • fs.realpathSync throws if appPath missing - Dropped: appPath is validated earlier in the call chain and is practically always valid at this point
  • Copy lock doesn't protect other file ops in processFullResponseActions - Dropped: Out of scope for this PR which specifically targets concurrent copy races (issue Prevent concurrent copy_file (git concurrency error)Β #2804). Other file operations worked without locking before this PR.
  • TypeScript definite assignment for resolve variable - Dropped: Promise constructor executor runs synchronously, so resolve is always assigned before use. Standard TypeScript pattern that compiles without error.
  • Raw error object rendered as [object Object] - Dropped: Pre-existing error handling behavior, not introduced by this PR

Generated by Dyadbot multi-agent code review

Comment on lines +100 to +101
// Copy the file (do not follow symlinks at destination)
fs.copyFileSync(fromFullPath, toFullPath);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟑 MEDIUM | security / misleading-comment

Misleading comment + missing destination symlink validation

The comment // Copy the file (do not follow symlinks at destination) is incorrect β€” fs.copyFileSync(src, dest) without any mode flag does follow symlinks at the destination. If toFullPath is or contains a symlink pointing outside the app directory, this call writes the copied content to that external target.

The source path now has thorough symlink-traversal validation via realpathSync, but the destination path (toFullPath) has no equivalent check. This is an inconsistency: an attacker who can create a symlink within the app directory could redirect the copy to overwrite an arbitrary file outside it.

πŸ’‘ Suggestion: Either (1) add a realpathSync check on the parent directory of toFullPath after mkdirSync and validate it stays within resolvedAppPath, or (2) remove the misleading comment and note this as a follow-up security hardening task.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 235 1 7 6

Summary: 235 passed, 1 failed, 7 flaky, 6 skipped

Failed Tests

🍎 macOS

  • select_component.spec.ts > deselect individual component from multiple
    • TimeoutError: locator.click: Timeout 120000ms exceeded.

πŸ“‹ Re-run Failing Tests (macOS)

Copy and paste to re-run all failing spec files locally:

npm run e2e \
  e2e-tests/select_component.spec.ts

⚠️ Flaky Tests

🍎 macOS

  • import.spec.ts > import app (passed after 1 retry)
  • local_agent_auto.spec.ts > local-agent - auto model (passed after 1 retry)
  • local_agent_basic.spec.ts > local-agent - dump request (passed after 1 retry)
  • refresh.spec.ts > refresh app (passed after 1 retry)
  • refresh.spec.ts > spa navigation inside iframe does not change iframe src attribute (passed after 1 retry)
  • select_component.spec.ts > deselect component (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)

πŸ“Š View full report

@wwwillchen wwwillchen merged commit 262c205 into dyad-sh:main Mar 2, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

Prevent concurrent copy_file (git concurrency error)

2 participants