feat: symlink Python venvs in worktrees for instant setup#1832
feat: symlink Python venvs in worktrees for instant setup#1832
Conversation
Switch venv strategy from RECREATE to SYMLINK so worktree creation is near-instant (matching node_modules behavior). A health check after symlinking verifies the venv is usable; if it fails, falls back to recreate with improved robustness: - Marker-based completion tracking (.setup_complete) detects incomplete venvs - Popen-based subprocess management with proper terminate/kill on timeout - Increased pip install timeout from 120s to 300s Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughSwitches Python venv handling to a symlink-first strategy with a runtime health check and automatic fallback to recreate; adds a venv setup completion marker and subprocess timeout/cleanup helpers; updates dependency strategy mappings, frontend worktree handlers, and tests to match the new flow. Changes
Sequence Diagram(s)sequenceDiagram
participant Frontend as Worktree Handler
participant Backend as Setup Manager
participant FS as Filesystem
participant Python as Python Runtime
participant Recreate as Recreate Flow
Frontend->>Backend: request setup for dependency (`venv`/`.venv`)
Backend->>FS: create symlink for venv
FS-->>Backend: symlink created
Backend->>Python: run health-check command in symlinked venv
alt health check succeeds
Python-->>Backend: success
Backend->>FS: write `.setup_complete` marker
Backend-->>Frontend: setup complete
else health check fails
Python-->>Backend: failure
Backend->>FS: remove broken symlink
Backend->>Recreate: create venv, run pip install (with timeouts)
Recreate-->>Backend: recreate success/failure (with logs)
Backend->>FS: write `.setup_complete` marker (on success)
Backend-->>Frontend: setup complete (or error)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @AndyMik90, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly improves the efficiency and reliability of Python virtual environment management within worktrees. By switching the default strategy for venvs from recreation to symlinking, worktree creation becomes much faster. To ensure robustness, a health check is now performed on symlinked venvs, with an automatic fallback to a more resilient recreate strategy if issues are detected. The recreate process itself has also been made more robust by tracking completion with a marker file, improving process termination on timeouts, and extending pip installation time limits. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
| ] | ||
| } | ||
|
|
||
| results = setup_worktree_dependencies(project_dir, worktree_path, project_index) |
Check notice
Code scanning / CodeQL
Unused local variable Note test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 days ago
In general, to fix an unused local variable you either (1) remove the variable and, if needed, keep only the right-hand side expression for its side effects, or (2) rename the variable to a conventionally "unused" name (e.g. _ or unused_results) if you intentionally keep it as documentation or for interface stability.
Here, the test test_venv_symlinked_when_source_exists does not use results at all. The behavior under test is that setup_worktree_dependencies creates a .venv symlink in the worktree. We should therefore keep the call (for its side effects) but drop the unused assignment. The minimal, behavior-preserving change is to replace results = setup_worktree_dependencies(...) with just setup_worktree_dependencies(...) on line 609 in tests/test_worktree_dependencies.py. No new imports or helper methods are needed.
| @@ -606,7 +606,7 @@ | ||
| ] | ||
| } | ||
|
|
||
| results = setup_worktree_dependencies(project_dir, worktree_path, project_index) | ||
| setup_worktree_dependencies(project_dir, worktree_path, project_index) | ||
|
|
||
| target = worktree_path / ".venv" | ||
| # The symlink should have been created (regardless of health check outcome) |
There was a problem hiding this comment.
Code Review
This pull request introduces a significant performance improvement for Python worktree setup by switching to symlinking virtual environments instead of recreating them. The implementation is robust, including a health check for the symlinked venv and a fallback to an improved recreation strategy. The recreation strategy itself is also enhanced with completion markers and more reliable process handling. The changes are consistently applied across both the Python backend and the TypeScript frontend, and the new logic is well-covered by tests. I've added a few suggestions for minor improvements in error handling and code duplication. Overall, this is a great enhancement.
apps/backend/core/workspace/setup.py
Outdated
| debug_warning(MODULE, f"{label} did not terminate, killing process") | ||
| proc.kill() | ||
| proc.wait(timeout=5) |
There was a problem hiding this comment.
To make the cleanup process more robust against the unlikely event that proc.wait() times out after proc.kill(), you can wrap it in a try...except block. This ensures the original TimeoutExpired exception is always the one that propagates from this handler, preventing an unhandled exception from within an exception handler.
debug_warning(MODULE, f"{label} did not terminate, killing process")
proc.kill()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
debug_warning(
MODULE, f"Process for '{label}' did not stop even after kill. File locks may persist."
)| if venv_path.exists(): | ||
| shutil.rmtree(venv_path, ignore_errors=True) | ||
| return False |
There was a problem hiding this comment.
The logic to clean up a partially created virtual environment is repeated multiple times in this function (here and in the pip install error handling paths). To improve maintainability and reduce code duplication, consider extracting this cleanup logic into a dedicated helper function, for example:
def _cleanup_failed_venv(venv_path: Path):
"""Safely removes a venv directory if it exists."""
if venv_path.exists():
shutil.rmtree(venv_path, ignore_errors=True)You could then call _cleanup_failed_venv(venv_path) in each of the failure scenarios.
| } catch { | ||
| debugLog('[TerminalWorktree] Symlinked venv health check failed, falling back to recreate:', config.sourceRelPath); |
There was a problem hiding this comment.
This catch block is bare, which means any error details from the failed health check are swallowed. For better debugging, it's good practice to capture the error object and include it in the log. Using debugError would be appropriate here.
| } catch { | |
| debugLog('[TerminalWorktree] Symlinked venv health check failed, falling back to recreate:', config.sourceRelPath); | |
| } catch (error) { | |
| debugError('[TerminalWorktree] Symlinked venv health check failed, falling back to recreate:', config.sourceRelPath, error); |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@apps/backend/core/workspace/dependency_strategy.py`:
- Around line 12-15: Update the DependencyStrategy enum docstring (the
DependencyStrategy class / enum in models.py) to reflect that SYMLINK is now
used for Python venv and .venv with a runtime health-check-and-fallback approach
rather than being outright unsafe; specifically, replace the statement "Python
venv breaks when symlinked" with a note that symlinking is supported for
venv/.venv but a post-symlink health check is performed and, on failure, the
system falls back to recreating the venv (aligning the docstring with the
mapping in dependency_strategy.py where SYMLINK is applied to venv/.venv and the
module docstring describes the health-check strategy).
In `@apps/backend/core/workspace/setup.py`:
- Around line 747-778: _popen_with_cleanup can deadlock if
proc.communicate(timeout=...) times out because stdout/stderr pipes remain full;
update the TimeoutExpired handler to drain or close the pipes before waiting for
termination: when catching subprocess.TimeoutExpired, after proc.terminate()
attempt to call proc.communicate() without a timeout (or read remaining
stdout/stderr) to drain the pipes, then proceed to proc.wait(timeout=10); if
that second wait times out, call proc.kill() and again drain/close pipes and
wait(proc.wait(timeout=5)) before re-raising; reference symbols: function
_popen_with_cleanup, local proc, proc.communicate, proc.terminate, proc.kill,
and the timeout wait calls.
- Around line 634-666: The health-check branch records the outcome under the
original "symlink" strategy even when the venv health-check fails and you fall
back to recreate; update this by ensuring strategy_name can change when you call
_apply_recreate_strategy and only append to results after the entire if/elif
chain so the final strategy_name reflects any fallback. Concretely: when the
subprocess/OSError except block calls _apply_recreate_strategy (and sets
performed), also update strategy_name to the recreate strategy (or allow
strategy_name to be reassigned by that call) and move the results bookkeeping
that appends to results[] to after the full venv/venv-symlink/clone handling so
the recorded entry uses the possibly-updated strategy_name (referencing
performed, config.dep_type, _apply_recreate_strategy, and strategy_name).
In `@apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts`:
- Around line 368-385: The rmSync call in the venv health-check fallback should
include recursive: true to handle Windows junctions (directories) so removal
doesn't fail with EISDIR/EPERM; in the catch block that currently does
rmSync(path.join(worktreePath, config.sourceRelPath), { force: true }) update
the options to include recursive: true (matching the pattern used later in
applyRecreateStrategy), then re-run the recreate flow by calling
applyRecreateStrategy(projectPath, worktreePath, config) as before.
In `@tests/test_worktree_dependencies.py`:
- Around line 588-613: The test test_venv_symlinked_when_source_exists is
misleading because setup_worktree_dependencies runs a venv health check (via
subprocess.run) which will fail for the minimal fake venv and cause recreation;
either make the health check succeed by mocking subprocess.run in this test (so
setup_worktree_dependencies will take the symlink path and you can assert
is_symlink()) or rename the test to reflect current behavior (e.g.,
test_venv_exists_after_setup_regardless_of_strategy) and tighten the assertion
to check target.exists() only; update references to the test name and keep
setup_worktree_dependencies and subprocess.run in mind when locating the logic
to change.
- Around line 615-639: The test test_venv_health_check_fallback_to_recreate only
ensures no exception by asserting isinstance(results, dict) but doesn't verify
the recreate fallback actually ran; update the test to assert the results
include the expected .venv entry (e.g. check results contains a key or path for
".venv" or that the returned dict records a recreate action), or monkeypatch/spy
the internal helper _apply_recreate_strategy (or whatever handler
setup_worktree_dependencies calls) and assert it was invoked when the symlink
health check fails; use the existing project_index and results variables to
perform the assertion so the test proves fallback behavior rather than just
non-crash.
| def test_venv_symlinked_when_source_exists(self, tmp_path: Path): | ||
| """Venv is symlinked (not recreated) when source venv exists.""" | ||
| from core.workspace.setup import setup_worktree_dependencies | ||
|
|
||
| project_dir = tmp_path / "project" | ||
| project_dir.mkdir() | ||
| venv_dir = project_dir / ".venv" | ||
| venv_dir.mkdir() | ||
| # Create a minimal venv structure so the symlink target looks real | ||
| (venv_dir / "bin").mkdir() | ||
| (venv_dir / "lib").mkdir() | ||
|
|
||
| worktree_path = tmp_path / "worktree" | ||
| worktree_path.mkdir() | ||
|
|
||
| project_index = { | ||
| "dependency_locations": [ | ||
| {"type": ".venv", "path": ".venv", "service": "backend"}, | ||
| ] | ||
| } | ||
|
|
||
| results = setup_worktree_dependencies(project_dir, worktree_path, project_index) | ||
|
|
||
| target = worktree_path / ".venv" | ||
| # The symlink should have been created (regardless of health check outcome) | ||
| assert target.exists() or target.is_symlink() |
There was a problem hiding this comment.
Test name and docstring are misleading — the venv will actually be recreated, not symlinked.
The test test_venv_symlinked_when_source_exists states "Venv is symlinked (not recreated) when source venv exists." However, the fake venv has no Python binary, so the health check will always fail and the code will fall back to recreate. The assertion target.exists() or target.is_symlink() passes in both cases, masking what actually happened.
Consider either:
- Mocking
subprocess.runfor the health check to make it pass, confirming the symlink path works end-to-end, or - Renaming the test to reflect what it actually verifies (e.g.,
test_venv_exists_after_setup_regardless_of_strategy).
🤖 Prompt for AI Agents
In `@tests/test_worktree_dependencies.py` around lines 588 - 613, The test
test_venv_symlinked_when_source_exists is misleading because
setup_worktree_dependencies runs a venv health check (via subprocess.run) which
will fail for the minimal fake venv and cause recreation; either make the health
check succeed by mocking subprocess.run in this test (so
setup_worktree_dependencies will take the symlink path and you can assert
is_symlink()) or rename the test to reflect current behavior (e.g.,
test_venv_exists_after_setup_regardless_of_strategy) and tighten the assertion
to check target.exists() only; update references to the test name and keep
setup_worktree_dependencies and subprocess.run in mind when locating the logic
to change.
| def test_venv_health_check_fallback_to_recreate(self, tmp_path: Path): | ||
| """When symlinked venv health check fails, falls back to recreate.""" | ||
| from core.workspace.setup import setup_worktree_dependencies | ||
|
|
||
| project_dir = tmp_path / "project" | ||
| project_dir.mkdir() | ||
| # Create a source venv that has no python binary (health check will fail) | ||
| venv_dir = project_dir / ".venv" | ||
| venv_dir.mkdir() | ||
|
|
||
| worktree_path = tmp_path / "worktree" | ||
| worktree_path.mkdir() | ||
|
|
||
| project_index = { | ||
| "dependency_locations": [ | ||
| {"type": ".venv", "path": ".venv", "service": "backend"}, | ||
| ] | ||
| } | ||
|
|
||
| # This should symlink, then health check fails (no python binary), | ||
| # then fall back to recreate (which will also fail since no real python | ||
| # in source). The important thing is it doesn't raise. | ||
| results = setup_worktree_dependencies(project_dir, worktree_path, project_index) | ||
| # Should not crash | ||
| assert isinstance(results, dict) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Fallback smoke test is reasonable but weak.
test_venv_health_check_fallback_to_recreate only asserts isinstance(results, dict), which verifies the code doesn't crash but doesn't confirm the fallback actually occurred. Consider asserting that the result contains the .venv path (indicating recreate succeeded), or patching _apply_recreate_strategy to verify it was called.
🤖 Prompt for AI Agents
In `@tests/test_worktree_dependencies.py` around lines 615 - 639, The test
test_venv_health_check_fallback_to_recreate only ensures no exception by
asserting isinstance(results, dict) but doesn't verify the recreate fallback
actually ran; update the test to assert the results include the expected .venv
entry (e.g. check results contains a key or path for ".venv" or that the
returned dict records a recreate action), or monkeypatch/spy the internal helper
_apply_recreate_strategy (or whatever handler setup_worktree_dependencies calls)
and assert it was invoked when the symlink health check fails; use the existing
project_index and results variables to perform the assertion so the test proves
fallback behavior rather than just non-crash.
AndyMik90
left a comment
There was a problem hiding this comment.
🤖 Auto Claude PR Review
Merge Verdict: 🟠 NEEDS REVISION
🟠 Needs revision - 1 issue(s) require attention.
Branch is out of date with base branch. Update branch first - if no conflicts arise, you can merge. If merge conflicts arise, resolve them and run follow-up review again. 1 issue(s) must be addressed (0 required, 1 recommended). 1 non-blocking suggestion(s) to consider.
Risk Assessment
| Factor | Level | Notes |
|---|---|---|
| Complexity | Medium | Based on lines changed |
| Security Impact | None | Based on security findings |
| Scope Coherence | Good | Based on structural review |
🚨 Blocking Issues (Must Fix)
- Branch Out of Date: PR branch is behind the base branch and needs to be updated
Findings Summary
- Medium: 1 issue(s)
- Low: 1 issue(s)
Generated by Auto Claude PR Review
Findings (2 selected of 2 total)
🔵 [e19dbecadab6] [LOW] Missing recursive flag in symlink removal during health check fallback
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:382
In the TypeScript health check fallback path (line 382), rmSync is called with only { force: true } when removing a broken symlink/junction before falling back to recreate. Every other rmSync call for venv paths in this same file uses { recursive: true, force: true } (lines 476, 510, 555). The Python equivalent properly differentiates between symlinks (unlink()) and directories (shutil.rmtree()). While this doesn't cause a bug in practice — the catch block silences failures, and the subsequent applyRecreateStrategy handles cleanup with { recursive: true, force: true } (line 476) — it creates an unnecessary failure-and-recovery cycle on Windows where junctions/symlinks-to-directories may require recursive: true in some Node.js versions.
Suggested fix:
Add `recursive: true` for consistency: `try { rmSync(path.join(worktreePath, config.sourceRelPath), { recursive: true, force: true }); } catch { /* best-effort */ }`
🟡 [a89c1a099f57] [MEDIUM] Unguarded symlink removal skips recreate fallback on failure
📁 apps/backend/core/workspace/setup.py:660
In the Python health check fallback path (setup.py lines 659-666), symlink_path.unlink() is NOT wrapped in a try/except. If it raises an error (e.g., PermissionError on Windows when a junction is locked by another process), the exception propagates out of the except (subprocess.SubprocessError, OSError) handler at line 653, gets caught by the outer except Exception at line 677, and _apply_recreate_strategy() is never called. The worktree ends up with no usable venv and no fallback attempt.
The TypeScript version correctly wraps the equivalent rmSync call in try/catch (line 382), ensuring applyRecreateStrategy always executes regardless of whether symlink removal succeeded.
Triggering scenario:
- Symlink is created successfully
- Health check fails (SubprocessError/OSError caught)
symlink_path.unlink()raises PermissionError (Windows file lock)- PermissionError propagates → caught by outer
except Exception _apply_recreate_strategyis never called- Dependency setup silently fails — no venv in worktree
Suggested fix:
Wrap the symlink removal in a try/except to match the TypeScript implementation:
```python
try:
if symlink_path.is_symlink():
symlink_path.unlink()
elif symlink_path.exists():
shutil.rmtree(symlink_path, ignore_errors=True)
except OSError:
pass # Best-effort removal; recreate strategy handles existing paths
performed = _apply_recreate_strategy(
project_dir, worktree_path, config
)
This ensures the recreate fallback always executes, and _apply_recreate_strategy already handles the case where the path still exists (lines 793-802).
---
*This review was generated by Auto Claude.*
apps/backend/core/workspace/setup.py
Outdated
| subprocess.run( | ||
| [python_bin, "-c", "import sys; print(sys.prefix)"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=10, | ||
| check=True, | ||
| ) |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
- Wrap symlink removal in try/except in Python health check fallback to prevent PermissionError from skipping recreate strategy - Add recursive: true flag to TypeScript rmSync call for consistent Windows junction handling Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AndyMik90
left a comment
There was a problem hiding this comment.
🤖 Auto Claude PR Review
🟠 Follow-up Review: Needs Revision
🟠 Needs revision - 3 blocking issue(s) require fixes.
Resolution Status
- ✅ Resolved: 2 previous findings addressed
- ❌ Unresolved: 0 previous findings remain
- 🆕 New Issues: 4 new findings in recent changes
Finding Validation
- 🔍 Dismissed as False Positives: 1 findings were re-investigated and found to be incorrect
- ✓ Confirmed Valid: 4 findings verified as genuine issues
- 👤 Needs Human Review: 0 findings require manual verification
🚨 Blocking Issues
- Branch Out of Date: PR branch is behind the base branch and needs to be updated
- quality: Pipe resource leak and potential deadlock in _popen_with_cleanup timeout path
- quality: Venv health check skipped when symlink already exists on re-run
Verdict
CI status: ✅ All 20 checks passing — no CI blockers.
Both previous findings from the last review have been properly resolved:
- [e19dbecadab6] rmSync now uses { recursive: true, force: true } and is wrapped in try/catch ✅
- [a89c1a099f57] symlink_path.unlink() is now wrapped in try/except OSError ✅
However, 2 new MEDIUM-severity issues were identified and validated as genuine:
-
NEW-001 [MEDIUM]:
_popen_with_cleanup()usesproc.wait()instead ofproc.communicate()after terminate/kill, which per Python documentation can deadlock with piped stdout/stderr and leaks pipe file descriptors. Fix: replaceproc.wait()withproc.communicate()on both timeout-handler lines. -
CMT-002 [MEDIUM]: Venv health check is skipped on re-runs because
_apply_symlink_strategy()returns False when the symlink already exists, and the health check is guarded byif performed. If the source venv breaks between runs, the broken symlinked venv goes undetected. Fix: run the health check whenever a venv symlink exists, not only when freshly created.
Additionally, 2 LOW-severity issues were confirmed (non-blocking):
- NEW-002: Fallback-to-recreate result is logged under 'symlink' strategy name (cosmetic)
- NEW-003: DependencyStrategy docstring in models.py still says "Python venv breaks when symlinked", contradicting the new symlink-first behavior
1 finding (CMT-001 - sys.prefix not validated) was dismissed as a false positive: validating sys.prefix against the worktree path would cause false failures since symlinks intentionally point to the original venv.
Good progress — the author addressed both previous findings thoroughly. The 2 MEDIUM issues should be straightforward to fix.
Review Process
Agents invoked: resolution-verifier, new-code-reviewer, comment-analyzer, finding-validator
This is an AI-generated follow-up review using parallel specialist analysis with finding validation.
Findings (4 selected of 4 total)
🟡 [NEW-001] [MEDIUM] Pipe resource leak and potential deadlock in _popen_with_cleanup timeout path
📁 apps/backend/core/workspace/setup.py:776
After communicate() raises TimeoutExpired, the function calls proc.wait() (lines 776, 780) instead of proc.communicate() to drain pipe buffers. Python docs explicitly warn that proc.wait() with stdout=PIPE/stderr=PIPE can deadlock if the child writes enough to fill the OS pipe buffer. Pipe file descriptors are also leaked until garbage collection. This affects the pip install path (timeout=300s) which can produce substantial output.
Suggested fix:
Replace proc.wait(timeout=10) with proc.communicate(timeout=10) and proc.wait(timeout=5) with proc.communicate(timeout=5) in the TimeoutExpired handler.
🟡 [CMT-002] [MEDIUM] Venv health check skipped when symlink already exists on re-run
📁 apps/backend/core/workspace/setup.py:635
When _apply_symlink_strategy() returns False (symlink already exists), performed is False, and the health check at 'if performed and config.dep_type in ("venv", ".venv")' is skipped entirely. If the source venv is corrupted or deleted between worktree setup runs, the broken symlinked venv goes undetected silently.
Suggested fix:
Decouple the health check from the 'performed' flag. Run the health check whenever a venv symlink exists at the target path, not only when freshly created. For example: check if target is a symlink and dep_type is venv, regardless of whether _apply_symlink_strategy created it.
🔵 [NEW-002] [LOW] Venv fallback-to-recreate logged under 'symlink' strategy name
📁 apps/backend/core/workspace/setup.py:679
When a venv symlink health check fails and falls back to _apply_recreate_strategy(), the result is recorded under results['symlink'] because strategy_name is never updated. The user sees 'Dependencies (symlink): .venv' even though the venv was recreated.
Suggested fix:
After falling back to recreate, update strategy_name to 'recreate' or record under the correct key in the results dict.
🔵 [NEW-003] [LOW] DependencyStrategy docstring contradicts new symlink-first behavior
📁 apps/backend/core/workspace/models.py:278
The DependencyStrategy class docstring says 'Python venv breaks when symlinked' and warns about 'import failures and broken interpreters', which contradicts the new default of SYMLINK strategy for venvs. The dependency_strategy.py module docstring was updated but models.py was not.
Suggested fix:
Update the DependencyStrategy docstring in models.py to reflect the new symlink-first-with-fallback approach, consistent with the updated dependency_strategy.py narrative.
This review was generated by Auto Claude.
- Fix potential deadlock in _popen_with_cleanup by using proc.communicate() instead of proc.wait() to drain pipes after terminate/kill - Fix health check skip on re-runs by decoupling health check from 'performed' flag - now runs whenever venv symlink exists, detecting broken source venvs between runs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@apps/backend/core/workspace/setup.py`:
- Around line 915-919: The marker_path.touch() OSError is being silently
ignored; change the except block in the venv completion section (where
marker_path.touch() is called) to log the failure using the existing debug or
debug_warning facility instead of pass — include the exception details in the
debug_warning/debug log call so failures to write the marker are visible in
debug logs and easier to diagnose. Ensure you reference marker_path.touch() and
the surrounding try/except in setup.py when adding the debug_warning call.
- Around line 823-846: The venv creation block using _popen_with_cleanup can
raise OSError/FileNotFoundError if python_exec is missing, leaving a partial
venv; update the try/except around the call that invokes _popen_with_cleanup
(the block that logs "venv creation failed" and handles
subprocess.TimeoutExpired) to also catch OSError (or FileNotFoundError) in the
same except path, perform the same cleanup of venv_path (shutil.rmtree if
exists), print the same warning via print_status, and return False; reference
the python_exec variable, _popen_with_cleanup call, and venv_path so you add the
new exception handler adjacent to the existing subprocess.TimeoutExpired
handler.
In `@apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts`:
- Around line 368-385: The health-check is incorrectly gated on the local
variable `performed`, so existing symlinked venvs that become broken later are
never re-validated; remove the `performed &&` condition and instead check
whether the venv path actually exists or is a symlink before running the exec
health check. Add a small helper (e.g., `existsOrIsSymlink`) that uses
`existsSync`/`lstatSync` semantics to detect presence or symlink, then change
the `if` around the `execFileAsync` block to `if ((config.depType === 'venv' ||
config.depType === '.venv') && existsOrIsSymlink(venvPath))` so the
`execFileAsync`/`debugLog`/`applyRecreateStrategy` flow still runs when a
pre-existing symlink is present but broken.
- Around line 563-564: The catch block that swallows errors when writing the
completion marker (writeFileSync(markerPath, '')) should at minimum log the
caught error for debugging; update the anonymous catch in the
worktree-handlers.ts section around writeFileSync to catch the error into a
variable and call the existing debug logging utility (e.g., debugLog or
processLogger.debug) with a concise message including markerPath and the error,
so failures to write the marker are visible during debugging without changing
failure semantics.
apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts
Outdated
Show resolved
Hide resolved
AndyMik90
left a comment
There was a problem hiding this comment.
🤖 Auto Claude PR Review
🟠 Follow-up Review: Needs Revision
🟠 Needs revision - 2 unresolved finding(s) from previous review.
Resolution Status
- ✅ Resolved: 2 previous findings addressed
- ❌ Unresolved: 2 previous findings remain
- 🆕 New Issues: 5 new findings in recent changes
Finding Validation
- 🔍 Dismissed as False Positives: 1 findings were re-investigated and found to be incorrect
- ✓ Confirmed Valid: 6 findings verified as genuine issues
- 👤 Needs Human Review: 0 findings require manual verification
🚨 Blocking Issues
- Branch Out of Date: PR branch is behind the base branch and needs to be updated
- quality: Frontend health check gated on 'performed' — inconsistent with backend's decoupled logic
- quality: Broken symlinks invisible to _apply_recreate_strategy cleanup
- quality: Frontend applyRecreateStrategy does not detect broken symlinks
- quality: [FROM COMMENTS] Frontend health check inconsistency flagged by both Sentry and CodeRabbit
Verdict
CI status: ✅ All 20 checks passing — no CI blockers.
2 of 4 previous findings have been properly resolved:
- [NEW-001] Pipe resource leak/deadlock: FIXED — proc.communicate() now used instead of proc.wait() after terminate/kill.
- [CMT-002] Venv health check skipped on re-run: FIXED in backend — health check decoupled from
performedflag, runs whenever venv symlink exists.
2 previous findings remain unresolved (both LOW severity):
- [NEW-002] Strategy name not updated when falling back from symlink to recreate — misleading user output.
- [NEW-003] DependencyStrategy docstring in models.py still says "Python venv breaks when symlinked", contradicting the new symlink-first default.
3 new blocking issues found and confirmed valid:
- [HIGH] Frontend health check gated on
performedflag (worktree-handlers.ts:369), inconsistent with backend's decoupled logic. Pre-existing broken venv symlinks will not be detected on the frontend path. - [MEDIUM] Backend
_apply_recreate_strategyusesvenv_path.exists()which follows symlinks — broken symlinks return False and are not cleaned up, blocking future venv creation. - [MEDIUM] Frontend
applyRecreateStrategyhas the same broken-symlink detection issue usingexistsSync.
1 finding dismissed as false positive:
- Pipe FD leak after proc.kill() — SIGKILL is unblockable on POSIX, and Python's GC closes pipe FDs. Not a realistic concern.
The HIGH-severity frontend inconsistency is the most important fix: the backend was correctly updated to decouple the health check, but the frontend still gates on performed, defeating the purpose of the fix for terminal-initiated worktree setups.
Review Process
Agents invoked: resolution-verifier, new-code-reviewer, comment-analyzer, finding-validator
This is an AI-generated follow-up review using parallel specialist analysis with finding validation.
Findings (7 selected of 7 total)
🔵 [NEW-002] [LOW] [UNRESOLVED] Venv fallback-to-recreate logged under 'symlink' strategy name
📁 apps/backend/core/workspace/setup.py:679
When a venv symlink health check fails and falls back to _apply_recreate_strategy(), the result is recorded under results['symlink'] because strategy_name is never updated. The user sees 'Dependencies (symlink): .venv' even though the venv was recreated.
Resolution note: Line 626: strategy_name = config.strategy.value (set to 'symlink'). Lines 670-671: performed = _apply_recreate_strategy(...) after fallback. Line 682: results[strategy_name].append(...) records result under 'symlink'. strategy_name is never updated to 'recreate' when falling back.
Suggested fix:
After falling back to recreate, update strategy_name to 'recreate' or record under the correct key in the results dict.
🔵 [NEW-003] [LOW] [UNRESOLVED] DependencyStrategy docstring contradicts new symlink-first behavior
📁 apps/backend/core/workspace/models.py:278
The DependencyStrategy class docstring says 'Python venv breaks when symlinked' and warns about 'import failures and broken interpreters', which contradicts the new default of SYMLINK strategy for venvs. The dependency_strategy.py module docstring was updated but models.py was not.
Resolution note: models.py lines 281-287: docstring still reads 'Python venv breaks when symlinked because CPython's pyvenv.cfg discovery walks the real directory hierarchy without resolving symlinks first (CPython bug #106045)... causing import failures and broken interpreters.' dependency_strategy.py was updated but models.py was not.
Suggested fix:
Update the DependencyStrategy docstring in models.py to reflect the new symlink-first-with-fallback approach, consistent with the updated dependency_strategy.py narrative.
🟠 [NCR-001] [HIGH] Frontend health check gated on 'performed' — inconsistent with backend's decoupled logic
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:369
The backend health check (setup.py:636-639) runs whenever a venv symlink exists, regardless of whether _apply_symlink_strategy just created it. The frontend at line 369 gates on 'if (performed && ...)', meaning when applySymlinkStrategy returns false (symlink already exists from a previous run), the health check is skipped entirely. Pre-existing broken venv symlinks in terminal-initiated worktrees will never be detected or repaired. This defeats the purpose of the CMT-002 fix applied to the backend.
Suggested fix:
Change line 369 from 'if (performed && (config.depType === ...))' to check venv path existence independently: 'if (config.depType === 'venv' || config.depType === '.venv') { const venvPath = path.join(worktreePath, config.sourceRelPath); if (existsSync(venvPath)) { /* run health check */ } }'
🟡 [NCR-003] [MEDIUM] Broken symlinks invisible to _apply_recreate_strategy cleanup
📁 apps/backend/core/workspace/setup.py:799
_apply_recreate_strategy uses venv_path.exists() (line 799) which follows symlinks — a broken symlink returns False, so the incomplete-venv cleanup is skipped. If a broken symlink persists at the path (e.g., from a failed previous setup), python -m venv fails because the symlink still occupies the filesystem entry. When called from the symlink fallback path this is mitigated (lines 661-669 clean up first), but when called directly from the RECREATE strategy branch, broken symlinks are not handled.
Suggested fix:
Change line 799 from 'if venv_path.exists():' to 'if venv_path.exists() or venv_path.is_symlink():' and handle the symlink case by unlinking it before proceeding with recreation.
🟡 [NCR-005] [MEDIUM] Frontend applyRecreateStrategy does not detect broken symlinks
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:469
Same issue as NCR-003 in the frontend. existsSync(venvPath) at line 469 follows symlinks — a broken symlink returns false, skipping cleanup. The function lacks a lstatSync check (unlike applySymlinkStrategy at lines 426-432 which already handles broken symlinks correctly).
Suggested fix:
Add a broken-symlink check before the existsSync check: try { lstatSync(venvPath); } catch { /* truly absent */ }. If lstat succeeds but existsSync fails, the entry is a broken symlink and should be removed.
🔵 [NCR-004] [LOW] Venv creation try/except missing OSError catch, inconsistent with pip install block
📁 apps/backend/core/workspace/setup.py:839
The venv creation try/except (lines 823-846) only catches subprocess.TimeoutExpired. If python_exec doesn't exist, FileNotFoundError propagates to the outer except Exception handler which only logs a debug warning — no user-facing warning via print_status. The pip install block at lines 909-913 correctly catches both TimeoutExpired and OSError, making this an internal inconsistency.
Suggested fix:
Add 'except OSError as e:' after the TimeoutExpired handler, matching the pip install block pattern: log via debug_warning and print_status, clean up partial venv, return False.
🟡 [CMT-001] [MEDIUM] [FROM COMMENTS] Frontend health check inconsistency flagged by both Sentry and CodeRabbit
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:369
Both @sentry[bot] and CodeRabbit independently identified that the frontend health check is gated on 'performed' (line 369), inconsistent with the backend's decoupled logic. The backend was fixed in commit fd09f87 but the frontend was not updated to match. This was the highest-confidence finding from the comment analysis, corroborated by two independent AI tools. Merged with NCR-001.
Suggested fix:
Mirror the backend pattern: check venv path existence independently of the performed flag.
This review was generated by Auto Claude.
…k logic Backend fixes: - Detect broken symlinks in _apply_recreate_strategy using is_symlink() - Add OSError catch in venv creation block for missing python_exec - Update strategy_name to 'recreate' when health check triggers fallback - Log marker write failures via debug_warning instead of silent pass - Update DependencyStrategy docstring to reflect symlink-first approach Frontend fixes: - Decouple health check from 'performed' flag - run on any existing venv - Add isSymlinkOrJunction() helper for broken symlink detection - Detect broken symlinks in applyRecreateStrategy before existsSync check - Log marker write failures instead of silent catch All blocking and medium-severity review findings addressed: - [NCR-001/CMT-001] Frontend health check now runs on re-runs - [NCR-003] Backend recreate detects broken symlinks - [NCR-005] Frontend recreate detects broken symlinks - [NEW-002] Strategy name correctly updated on fallback - [NCR-004] OSError handling added to venv creation - [NEW-003] DependencyStrategy docstring updated - Marker write failures now logged for debugging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/backend/core/workspace/setup.py (1)
670-685:⚠️ Potential issue | 🔴 Critical
KeyErrorwhen fallback changesstrategy_nameto"recreate"but the key doesn't exist inresults.Line 675 sets
strategy_name = "recreate", but theresultsdict was only initialized with"symlink"at line 627–628. When line 685 executesresults[strategy_name].append(...), it will raise aKeyErrorif no other config already created a"recreate"entry.Proposed fix
# Update strategy name to reflect fallback if performed: strategy_name = "recreate" + if strategy_name not in results: + results[strategy_name] = [] elif config.strategy == DependencyStrategy.RECREATE:Alternatively, move the
resultsbookkeeping to after the fullif/elifchain with a guard:if performed: + if strategy_name not in results: + results[strategy_name] = [] results[strategy_name].append(config.source_rel_path)
🤖 Fix all issues with AI agents
In `@apps/backend/core/workspace/setup.py`:
- Line 662: The variable symlink_path is redundant because it's identical to
venv_path (both set to worktree_path / config.source_rel_path); remove the
redundant assignment "symlink_path = worktree_path / config.source_rel_path" and
replace any subsequent uses of symlink_path with venv_path in the same scope
(e.g., in the function or block where venv_path and symlink_path are defined) to
simplify the code and avoid duplicate variables.
AndyMik90
left a comment
There was a problem hiding this comment.
🤖 Auto Claude PR Review
🟠 Follow-up Review: Needs Revision
🟠 Needs revision - 3 blocking issue(s) require fixes.
Resolution Status
- ✅ Resolved: 7 previous findings addressed
- ❌ Unresolved: 0 previous findings remain
- 🆕 New Issues: 4 new findings in recent changes
Finding Validation
- 🔍 Dismissed as False Positives: 0 findings were re-investigated and found to be incorrect
- ✓ Confirmed Valid: 3 findings verified as genuine issues
- 👤 Needs Human Review: 1 findings require manual verification
🚨 Blocking Issues
- Branch Out of Date: PR branch is behind the base branch and needs to be updated
- quality: KeyError when venv symlink falls back to recreate strategy
- quality: isSymlinkOrJunction does not actually detect Windows junctions
Verdict
All 20 CI checks are passing. All 7 previous findings have been resolved — excellent progress addressing review feedback. However, 2 new confirmed issues were identified:
-
[HIGH] KeyError on venv symlink fallback path (NEW-001): When a venv symlink health check fails and the fallback recreate succeeds,
strategy_nameis changed to"recreate"butresults["recreate"]was never initialized, causing aKeyErroratresults[strategy_name].append(...). While the outerexcept Exceptionprevents a crash, it silently drops the successful recreate result and logs a misleading warning. This was independently flagged by both the new-code-reviewer and CodeRabbit. One-line fix:results.setdefault(strategy_name, []).append(...). -
[MEDIUM] isSymlinkOrJunction doesn't detect Windows junctions (NEW-002): The function only checks
lstatSync().isSymbolicLink()which returnsfalsefor Windows junctions, butapplySymlinkStrategycreates junctions on Windows viasymlinkSync(..., 'junction'). Broken junctions won't be detected during health checks on Windows.
Both HIGH and MEDIUM findings are confirmed valid and should be addressed before merge. The fixes are straightforward (one-liner for NEW-001, small refactor for NEW-002).
Review Process
Agents invoked: resolution-verifier, new-code-reviewer, comment-analyzer, finding-validator
This is an AI-generated follow-up review using parallel specialist analysis with finding validation.
Findings (4 selected of 4 total)
🟠 [NEW-001] [HIGH] KeyError when venv symlink falls back to recreate strategy
📁 apps/backend/core/workspace/setup.py:685
When a venv symlink health check fails and the fallback to recreate succeeds, strategy_name is updated to 'recreate' (line 675) but results['recreate'] was never initialized. The results dict is only initialized with the original strategy key 'symlink' (lines 627-628). When line 685 executes results[strategy_name].append(...), it raises KeyError. While the outer except Exception prevents a crash, the successful recreate result is silently dropped and a misleading warning is logged.
Suggested fix:
Change line 685 from `results[strategy_name].append(config.source_rel_path)` to `results.setdefault(strategy_name, []).append(config.source_rel_path)`, or add `results.setdefault('recreate', [])` after line 675 where strategy_name is reassigned.
🟡 [NEW-002] [MEDIUM] isSymlinkOrJunction does not actually detect Windows junctions
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:64
The isSymlinkOrJunction function uses lstatSync().isSymbolicLink() which returns false for Windows junctions. However, applySymlinkStrategy creates junctions on Windows via symlinkSync(..., 'junction'). This means broken venv junctions on Windows are silently missed during health checks at lines 386 and 487, preventing automatic fallback to recreate.
Suggested fix:
Use readlinkSync in a try/catch instead of isSymbolicLink() — readlinkSync works for both symlinks and junctions. Alternatively, check for junctions by testing if the path exists via lstat but is broken (lstatSync succeeds but existsSync fails).
🔵 [NEW-003] [LOW] Potential resource leak if kill() followed by communicate() times out in _popen_with_cleanup
📁 apps/backend/core/workspace/setup.py:786
In _popen_with_cleanup, if proc.kill() followed by proc.communicate(timeout=5) also raises TimeoutExpired, the stdout/stderr pipes are not closed and the process may remain as a zombie. While extremely unlikely after SIGKILL on POSIX, a finally block would ensure deterministic cleanup.
Suggested fix:
Add a finally block or wrap the inner except in try/except to ensure proc.stdout.close(), proc.stderr.close(), and proc.wait() are called even if the final communicate() times out.
🔵 [CMT-002] [LOW] [FROM COMMENTS] Redundant variable symlink_path identical to venv_path
📁 apps/backend/core/workspace/setup.py:662
symlink_path = worktree_path / config.source_rel_path at line 662 computes the exact same value as venv_path defined at line 637. Both are in the same scope. symlink_path on lines 664-667 could be replaced with venv_path for clarity.
Suggested fix:
Replace symlink_path with venv_path on lines 662-667.
This review was generated by Auto Claude.
Fixes all issues from 2026-02-16 auto-claude review: - [HIGH] NEW-001: Fix KeyError when venv symlink falls back to recreate strategy by ensuring results dict has the 'recreate' key before appending - [MEDIUM] NEW-002: Fix isSymlinkOrJunction to detect Windows junctions by using readlinkSync instead of lstatSync().isSymbolicLink() - [LOW] NEW-003: Add finally block to _popen_with_cleanup to prevent resource leaks by ensuring pipes are closed and process is reaped - [LOW] CMT-002: Remove redundant symlink_path variable and reuse venv_path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts (1)
430-451:⚠️ Potential issue | 🟡 Minor
applySymlinkStrategysilently bails on broken symlinks without removing them.Lines 444-451 detect a broken symlink at
targetPathvialstatSyncand returnfalsewithout cleaning it up. For venv types, the health-check fallback insetupWorktreeDependencieshandles this. But for non-venv symlink types (e.g.,node_modules,vendor_php), a broken symlink will persist indefinitely — the function returnsfalseand no recovery path exists.Consider removing the stale symlink so a fresh one can be created:
Proposed fix: remove broken symlink before bailing
// Check for broken symlinks try { lstatSync(targetPath); - debugLog('[TerminalWorktree] Skipping symlink', config.sourceRelPath, '- target exists (possibly broken symlink)'); - return false; + // Target exists as a broken symlink — remove it so we can recreate + debugLog('[TerminalWorktree] Removing broken symlink at', config.sourceRelPath); + try { rmSync(targetPath, { force: true }); } catch { /* best-effort */ } } catch { // Target doesn't exist at all — good, we can create symlink }
🤖 Fix all issues with AI agents
In `@apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts`:
- Around line 383-404: The duplicated path join should use the already-declared
venvPath variable instead of recomputing path.join(worktreePath,
config.sourceRelPath); update the inner rmSync call to rmSync(venvPath, {
recursive: true, force: true }) so the removed target matches the health-check
path; keep the rest of the logic (execFileAsync check, debugLog messages, and
setting performed via applyRecreateStrategy) unchanged.
| // For venvs, verify the symlink is usable — fall back to recreate if not | ||
| // Run health check whenever a venv exists (not just on fresh creation) | ||
| if (config.depType === 'venv' || config.depType === '.venv') { | ||
| const venvPath = path.join(worktreePath, config.sourceRelPath); | ||
| // Check if venv path exists (as symlink or otherwise) | ||
| if (existsSync(venvPath) || isSymlinkOrJunction(venvPath)) { | ||
| const pythonBin = isWindows() | ||
| ? path.join(venvPath, 'Scripts', 'python.exe') | ||
| : path.join(venvPath, 'bin', 'python'); | ||
| try { | ||
| await execFileAsync(pythonBin, ['-c', 'import sys; print(sys.prefix)'], { | ||
| timeout: 10000, | ||
| }); | ||
| debugLog('[TerminalWorktree] Symlinked venv health check passed:', config.sourceRelPath); | ||
| } catch { | ||
| debugLog('[TerminalWorktree] Symlinked venv health check failed, falling back to recreate:', config.sourceRelPath); | ||
| // Remove the broken symlink and recreate | ||
| try { rmSync(path.join(worktreePath, config.sourceRelPath), { recursive: true, force: true }); } catch { /* best-effort */ } | ||
| performed = await applyRecreateStrategy(projectPath, worktreePath, config); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Health check correctly decoupled from performed — minor redundancy on line 400.
The health check now runs whenever a venv symlink/path exists regardless of whether a fresh symlink was just created. This addresses the prior review feedback.
One nit: line 400 rebuilds the path path.join(worktreePath, config.sourceRelPath), which is identical to venvPath already declared on line 386.
Use the existing `venvPath` variable
- try { rmSync(path.join(worktreePath, config.sourceRelPath), { recursive: true, force: true }); } catch { /* best-effort */ }
+ try { rmSync(venvPath, { recursive: true, force: true }); } catch { /* best-effort */ }🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts` around
lines 383 - 404, The duplicated path join should use the already-declared
venvPath variable instead of recomputing path.join(worktreePath,
config.sourceRelPath); update the inner rmSync call to rmSync(venvPath, {
recursive: true, force: true }) so the removed target matches the health-check
path; keep the rest of the logic (execFileAsync check, debugLog messages, and
setting performed via applyRecreateStrategy) unchanged.
AndyMik90
left a comment
There was a problem hiding this comment.
🤖 Auto Claude PR Review
🟠 Follow-up Review: Needs Revision
🟠 Needs revision - 2 blocking issue(s) require fixes.
Resolution Status
- ✅ Resolved: 4 previous findings addressed
- ❌ Unresolved: 0 previous findings remain
- 🆕 New Issues: 6 new findings in recent changes
Finding Validation
- 🔍 Dismissed as False Positives: 0 findings were re-investigated and found to be incorrect
- ✓ Confirmed Valid: 6 findings verified as genuine issues
- 👤 Needs Human Review: 0 findings require manual verification
🚨 Blocking Issues
- Branch Out of Date: PR branch is behind the base branch and needs to be updated
- quality: [FROM COMMENTS] Broken symlinks for non-venv types persist indefinitely (from CodeRabbit)
Verdict
Branch is out of date with base branch. Update branch first - if no conflicts arise, you can merge. If merge conflicts arise, resolve them and run follow-up review again.
Review Process
Agents invoked: resolution-verifier, new-code-reviewer, comment-analyzer, finding-validator
This is an AI-generated follow-up review using parallel specialist analysis with finding validation.
Findings (6 selected of 6 total)
🔵 [NEW-CODE-001] [LOW] performed=True default inconsistent with TypeScript performed=false
📁 apps/backend/core/workspace/setup.py:631
Python initializes performed = True before the strategy dispatch if/elif chain, while the TypeScript equivalent uses performed = false. If a new DependencyStrategy enum variant is added without updating the chain, the Python version would falsely record the path as successfully processed. Not an active bug since all 4 current strategies are handled.
Suggested fix:
Initialize `performed = False` to match the TypeScript implementation's safer default.
🔵 [NEW-CODE-002] [LOW] Misleading _popen_with_cleanup docstring about TimeoutExpired
📁 apps/backend/core/workspace/setup.py:767
Docstring states 'Raises subprocess.TimeoutExpired if the process could not be stopped' but the exception is always re-raised when the initial communicate() timeout is exceeded, regardless of whether cleanup succeeded.
Suggested fix:
Change docstring to: 'Raises subprocess.TimeoutExpired if the command exceeds the given timeout (after cleanup is attempted).'
🔵 [NEW-CODE-003] [LOW] applySymlinkStrategy uses lstatSync instead of new isSymlinkOrJunction helper
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:444
The new isSymlinkOrJunction helper (using readlinkSync) was added for reliable symlink/junction detection and is used in setupWorktreeDependencies and applyRecreateStrategy. However, applySymlinkStrategy still uses the older lstatSync approach. This is a minor consistency issue — both approaches work for detecting broken symlinks, but the dedicated helper should be used throughout.
Suggested fix:
Replace the lstatSync check at lines 444-451 with isSymlinkOrJunction(targetPath) for consistency.
🔵 [NEW-CODE-004] [LOW] TypeScript doesn't distinguish strategy in results when venv fallback occurs
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:397
When a venv symlink health check fails and falls back to recreate, the TypeScript version records the path in a flat processed array without noting the actual strategy used. The Python version explicitly changes strategy_name to 'recreate' and records under the correct key. This is a minor behavioral inconsistency affecting debug output.
Suggested fix:
Add a debug log like `debugLog('[TerminalWorktree] Venv fallback to recreate succeeded:', config.sourceRelPath)` after successful recreate.
🔵 [CMT-001] [LOW] [FROM COMMENTS] Redundant path.join reconstruction when venvPath is already in scope
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:400
Line 400 uses path.join(worktreePath, config.sourceRelPath) but venvPath with the identical value is already declared at line 386. Should reuse the existing variable for consistency.
Suggested fix:
Replace `path.join(worktreePath, config.sourceRelPath)` on line 400 with `venvPath`.
🟡 [CMT-002] [MEDIUM] [FROM COMMENTS] Broken symlinks for non-venv types persist indefinitely (from CodeRabbit)
📁 apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts:444
applySymlinkStrategy detects broken symlinks via lstatSync and returns false without removing them. For venv types, the health-check fallback handles this. For non-venv types (node_modules, vendor_php), a broken symlink persists forever. This is pre-existing behavior not introduced by this PR, but is worth addressing in a follow-up.
Suggested fix:
When a broken symlink is detected (lstatSync succeeds but existsSync fails), remove it before returning false so a fresh symlink can be created on retry.
This review was generated by Auto Claude.
Summary
python -c "import sys; print(sys.prefix)") — if it fails, automatically falls back to the improved recreate strategy.setup_completemarker tracking, Popen-based process cleanup on timeout, and increased pip timeout (120s → 300s)Test plan
pytest tests/test_worktree_dependencies.py -v)python -c "import dotenv"works inside the symlinked worktree venv🤖 Generated with Claude Code
Summary by CodeRabbit
Performance Improvements
Improvements
Tests