fix: inject .npmrc ignore-workspace=true into worktrees to prevent monorepo corruption#2016
Conversation
Release v2.7.6
…ndyMik90#2013) Worktrees are created inside the host repository at .auto-claude/worktrees/tasks/<id>/ and .auto-claude/worktrees/terminal/<name>/. When the host is a pnpm/npm workspace monorepo, running `pnpm install` inside the worktree causes pnpm to walk up, detect the parent pnpm-workspace.yaml as the shared workspace root, and silently re-link the parent's node_modules symlinks into the worktree's .pnpm store. After the worktree is removed, the parent's node_modules contains dozens of dangling symlinks pointing into a non-existent path, breaking builds. Fix: after creating any task or terminal worktree, write a .npmrc file containing `ignore-workspace=true`. This pnpm/npm setting prevents workspace root detection from walking above the worktree directory, so the worktree's package-manager operations remain fully isolated from the host repo. The .npmrc is written only if one does not already exist, so projects that carry their own .npmrc are unaffected. Co-Authored-By: Octopus <liyuan851277048@icloud.com>
|
octo-patch seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughBackend and frontend now ensure a worktree-local Changes
Sequence Diagram(s)sequenceDiagram
participant Frontend as Frontend IPC
participant Backend as Backend create_worktree
participant FS as Filesystem (.npmrc)
participant Logger as Logger
Frontend->>Backend: request createTerminalWorktree / create_worktree
Backend->>Backend: create git worktree
Backend->>FS: read `.npmrc` (if exists)
alt `.npmrc` contains ignore-workspace
Backend->>Logger: debug "ignore-workspace present"
else missing directive
Backend->>FS: append "\nignore-workspace=true" (UTF-8)
FS-->>Backend: write success
Backend->>Logger: debug "wrote ignore-workspace"
end
alt write/read error
Backend->>Logger: warn "failed to update .npmrc"
end
Backend-->>Frontend: return WorktreeInfo
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ 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
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 |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/backend/core/worktree.py (1)
656-663:⚠️ Potential issue | 🟡 MinorIdempotent reuse path skips the
.npmrcpatch.When
create_worktreeis called for a worktree that already exists and is registered (e.g., one created before this PR landed, or before the user updated), it returns early here without ever writing.npmrc. The nextpnpm installinside that pre-existing worktree will still corrupt the parent'snode_modules.Consider hoisting the
.npmrc-ensuring logic into a small helper and invoking it on both the "reuse existing" branch and the "freshly created" branch so the fix applies retroactively to existing worktrees too.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/backend/core/worktree.py` around lines 656 - 663, The idempotent reuse path in create_worktree returns early and skips writing the .npmrc patch; extract the .npmrc-ensuring logic into a small helper (e.g., ensure_worktree_npmrc(worktree_path) or _ensure_npmrc_for_worktree) and call it both when reusing an existing worktree (the branch that calls get_worktree_info and returns existing) and after the freshly created worktree path is set up; update create_worktree to invoke this helper before returning existing and after creating a new worktree so pre-existing worktrees get the same .npmrc fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/backend/core/worktree.py`:
- Around line 736-748: The current logic skips writing .npmrc when npmrc_path
exists, which allows a host-committed .npmrc to omit ignore-workspace and
reintroduce workspace linking; change the behavior in the worktree setup code
around npmrc_path and worktree_path so that you always ensure the directive is
present: if .npmrc does not exist write "ignore-workspace=true\n", otherwise
read the existing file (via npmrc_path.read_text()), check whether an
uncommented line containing "ignore-workspace=true" exists, and if not append
that line to the file (or write back with the added line), and keep the same
logging and OSError warning handling used around npmrc_path.write_text to
surface failures; reference npmrc_path, worktree_path, write_text and read_text
when locating and updating the code.
- Around line 736-741: Update the inline comment around the .npmrc write (the
block referencing "ignore-workspace") to state that ignore-workspace=true is
pnpm-specific and remove or change any phrasing that says "pnpm/npm" so it only
mentions pnpm; reference the exact setting name "ignore-workspace=true" and the
.npmrc file in the comment, and add a short note that npm and Yarn use
package.json workspaces and should be tracked separately (per WORKTREE_ROOT /
`#2013`) if needed.
In `@apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts`:
- Around line 837-851: The .npmrc write logic in createTerminalWorktree
duplicates the backend WorktreeManager.create_worktree behavior; extract the
literal string and explanatory comment ('ignore-workspace=true\n' plus the
pnpm/workspace rationale) into a shared constant in a common config/docs module
(e.g., a new exported constant used by both frontend
terminal/worktree-handlers.ts and backend core/worktree.py), update
createTerminalWorktree to import and use that constant instead of the inline
string/comment, and add a small test/fixture that asserts both implementations
produce an .npmrc containing "ignore-workspace=true" to prevent drift.
- Around line 843-851: The current check using existsSync(npmrcPath) skips
adding the ignore-workspace directive if a tracked .npmrc already exists; modify
the logic around npmrcPath (where existsSync, writeFileSync and
debugLog/debugError are used) to read the existing file when present, detect
whether the line "ignore-workspace=true" (exact match) is already present, and
if not append that line (with a terminating newline) to the file instead of
skipping; keep the current try/catch and logging (use debugLog on success and
debugError on failure) so both new-file and append cases are handled safely.
---
Outside diff comments:
In `@apps/backend/core/worktree.py`:
- Around line 656-663: The idempotent reuse path in create_worktree returns
early and skips writing the .npmrc patch; extract the .npmrc-ensuring logic into
a small helper (e.g., ensure_worktree_npmrc(worktree_path) or
_ensure_npmrc_for_worktree) and call it both when reusing an existing worktree
(the branch that calls get_worktree_info and returns existing) and after the
freshly created worktree path is set up; update create_worktree to invoke this
helper before returning existing and after creating a new worktree so
pre-existing worktrees get the same .npmrc fix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9d8fe1c1-3ad8-4aeb-a632-2515b2aa5ba7
📒 Files selected for processing (2)
apps/backend/core/worktree.pyapps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts
| # Write .npmrc to prevent pnpm/npm from walking up to the parent | ||
| # workspace root (pnpm-workspace.yaml / package.json#workspaces). | ||
| # Without this, running `pnpm install` inside the worktree silently | ||
| # re-links the parent repo's node_modules into the worktree's pnpm | ||
| # store, corrupting the parent's dependency graph after cleanup. | ||
| # See: https://pnpm.io/npmrc#ignore-workspace |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does pnpm npmrc setting ignore-workspace=true also affect npm or yarn workspace discovery?
💡 Result:
No. pnpm’s .npmrc setting ignore-workspace=true only affects pnpm’s own workspace detection/resolution. It does not change how npm or Yarn discover their workspaces. Reason (grounded): 1) pnpm documents that .npmrc is used for pnpm configuration in the npmrc format primarily for authentication/registry-style settings; other workspace-related behavior is configured via pnpm-workspace.yaml or pnpm’s pnpm config/config.yaml. This implies pnpm-specific ignore-workspace behavior is implemented inside pnpm, not as a cross-tool npmrc workspace-discovery toggle. 2) pnpm provides its own CLI/workspace-ignore mechanism (e.g., --ignore-workspace), and pnpm issues/documentation describe it as “pnpm ignores any workspace configuration found in the current or parent directories,” indicating the option is scoped to pnpm’s workspace logic. Practical takeaway: - If you want npm or Yarn to ignore their workspaces, you must use whatever mechanism those tools provide (there isn’t an npmrc setting documented here that makes them ignore pnpm-style workspaces). - Setting ignore-workspace=true in .npmrc won’t prevent npm/yarn from detecting their own workspaces (e.g., via package.json workspaces, yarn workspaces, or npm workspaces rules).
Citations:
- 1: https://pnpm.io/next/configuring
- 2: https://pnpm.io/next/settings
- 3:
--ignore-workspaceoption undocumented pnpm/pnpm#7802 - 4: https://pnpm.io/configuring
🌐 Web query:
npm workspaces configuration how does npm detect workspaces
💡 Result:
npm detects workspaces through the "workspaces" property in the root package.json file of a project. This property contains an array of glob patterns or direct paths pointing to directories containing package.json files that define the individual workspaces. When running npm commands like npm install, npm reads the "workspaces" field from the nearest ancestor package.json (walking up from the current working directory if necessary, with automatic workspace root detection). It then uses these patterns to traverse the filesystem, matching directories that contain valid package.json files. Valid workspaces are those nested packages explicitly defined by the workspaces configuration. Internally, npm uses the @npmcli/map-workspaces package to process the workspaces field: it expands globs (e.g., "packages/"), ignores patterns like "/node_modules/", reads package.json files in matching directories, extracts package names, and builds a map of package name to path. During installation, matching workspace dependencies are symlinked into node_modules instead of being fetched from the registry. Example configuration: { "name": "my-monorepo", "workspaces": ["packages/", "apps/*"] } This would detect all directories under packages/ and apps/ that have a package.json. The root package.json should typically have "private": true. Commands support --workspace=<name/path> or --workspaces flags to target specific or all workspaces.
Citations:
- 1: https://docs.npmjs.com/cli/v8/using-npm/workspaces/
- 2: https://docs.npmjs.com/cli/v8/using-npm/workspaces
- 3: https://github.com/npm/map-workspaces
- 4: https://github.com/npm/rfcs/blob/main/implemented/0026-workspaces.md
- 5: https://www.unpkg.com/npm@10.5.2/docs/output/using-npm/workspaces.html
🌐 Web query:
Yarn workspaces configuration how does Yarn detect workspaces
💡 Result:
Yarn detects workspaces through a root package.json file containing a "workspaces" field with glob patterns, and individual workspace directories each containing their own package.json files. The process works as follows: 1. Yarn identifies the project root by starting from the current working directory and walking up parent directories until it finds a directory containing a package.json file. This becomes the project root (cwd). 2. It loads the manifest (package.json) from this root directory. 3. From the root manifest's workspaceDefinitions (derived from the "workspaces" field, which is an array of glob patterns like ["packages/*"]), it extracts the patterns. 4. Using fastGlob, it resolves these glob patterns relative to the root cwd, with options: onlyDirectories: true, ignore: ['/node_modules', '/.git', '/.yarn']. 5. For each matching directory, it checks if a package.json exists. 6. Valid directories (those with package.json) become child workspaces of the root workspace. This applies to Yarn Berry (2+). The root workspace is always present, and child workspaces are discovered via globs during the Workspace setup method, called as part of Project initialization.
Citations:
- 1: https://yarnpkg.com/features/workspaces
- 2: https://yarnpkg.com/api/yarnpkg-core/class/Workspace
- 3: https://github.com/yarnpkg/berry/blob/4bd2b2111867ca3a9dc46438aa3010145e31910b/packages/yarnpkg-core/sources/Workspace.ts
- 4: [Bug?]: Workspace globbing does not respect gitignored files/dirs yarnpkg/berry#4572
- 5: https://www.yarnpkg.com/features/workspaces
- 6: https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-core/sources/Project.ts
Narrow comment to "pnpm" — ignore-workspace does not affect npm or Yarn.
The setting ignore-workspace=true is pnpm-specific. npm detects workspaces via the workspaces field in package.json (not .npmrc), and Yarn similarly uses the workspaces field in package.json. The .npmrc file does not control workspace detection for npm or Yarn. The code fix is correct for pnpm, but the comment's "pnpm/npm" wording is misleading — change it to reference pnpm only. Addressing npm and Yarn workspace handling in a worktree should be tracked separately as part of the WORKTREE_ROOT acceptance criterion from #2013.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/backend/core/worktree.py` around lines 736 - 741, Update the inline
comment around the .npmrc write (the block referencing "ignore-workspace") to
state that ignore-workspace=true is pnpm-specific and remove or change any
phrasing that says "pnpm/npm" so it only mentions pnpm; reference the exact
setting name "ignore-workspace=true" and the .npmrc file in the comment, and add
a short note that npm and Yarn use package.json workspaces and should be tracked
separately (per WORKTREE_ROOT / `#2013`) if needed.
| // Write .npmrc to prevent pnpm/npm from walking up to the parent | ||
| // workspace root (pnpm-workspace.yaml / package.json#workspaces). | ||
| // Without this, running `pnpm install` inside the worktree silently | ||
| // re-links the parent repo's node_modules into the worktree's pnpm | ||
| // store, corrupting the parent's dependency graph after cleanup. | ||
| // See: https://pnpm.io/npmrc#ignore-workspace | ||
| const npmrcPath = path.join(worktreePath, '.npmrc'); | ||
| if (!existsSync(npmrcPath)) { | ||
| try { | ||
| writeFileSync(npmrcPath, 'ignore-workspace=true\n', 'utf-8'); | ||
| debugLog('[TerminalWorktree] Wrote .npmrc (ignore-workspace=true) to worktree:', worktreePath); | ||
| } catch (npmrcError) { | ||
| debugError('[TerminalWorktree] Could not write .npmrc to worktree:', npmrcError); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Duplicated logic across backend and frontend — extract a shared constant at minimum.
This block is byte-for-byte equivalent (modulo language) to apps/backend/core/worktree.py:736-748. Two parallel implementations of the same monorepo-corruption mitigation will drift over time (e.g., when the "append if missing" fix above is applied, when extra pnpm settings need to be added, or when npm/yarn workspace handling lands per #2013). Since the backend WorktreeManager.create_worktree and this createTerminalWorktree run in independent processes, you can't share runtime code, but you can at least:
- Hoist the file content (
'ignore-workspace=true\n') and the comment block into a single source of truth (e.g., a constant in a shared docs/config file referenced from both implementations), and - Add a brief test or fixture asserting both paths produce an
.npmrcwithignore-workspace=trueso they stay in sync.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts` around
lines 837 - 851, The .npmrc write logic in createTerminalWorktree duplicates the
backend WorktreeManager.create_worktree behavior; extract the literal string and
explanatory comment ('ignore-workspace=true\n' plus the pnpm/workspace
rationale) into a shared constant in a common config/docs module (e.g., a new
exported constant used by both frontend terminal/worktree-handlers.ts and
backend core/worktree.py), update createTerminalWorktree to import and use that
constant instead of the inline string/comment, and add a small test/fixture that
asserts both implementations produce an .npmrc containing
"ignore-workspace=true" to prevent drift.
There was a problem hiding this comment.
Code Review
This pull request adds logic to both the backend and frontend to create an .npmrc file with ignore-workspace=true in new worktrees, preventing pnpm from corrupting the parent workspace's dependency graph. Feedback suggests that the current implementation is too restrictive because it skips the injection if an .npmrc file already exists. It is recommended to append the setting to existing files to ensure the fix works for repositories that already use .npmrc for other configurations.
| # store, corrupting the parent's dependency graph after cleanup. | ||
| # See: https://pnpm.io/npmrc#ignore-workspace | ||
| npmrc_path = worktree_path / ".npmrc" | ||
| if not npmrc_path.exists(): |
There was a problem hiding this comment.
The check if not npmrc_path.exists() will skip the injection of ignore-workspace=true if the repository already contains an .npmrc file at its root. Since monorepos (the primary target of this fix) frequently include a root .npmrc, this implementation will fail to prevent corruption in those cases unless the user has already manually added the flag. It would be more robust to check if the flag is present in the file and append it if missing, even if the file exists.
| // store, corrupting the parent's dependency graph after cleanup. | ||
| // See: https://pnpm.io/npmrc#ignore-workspace | ||
| const npmrcPath = path.join(worktreePath, '.npmrc'); | ||
| if (!existsSync(npmrcPath)) { |
There was a problem hiding this comment.
Similar to the backend implementation, checking !existsSync(npmrcPath) means the fix is skipped for any repository that already includes an .npmrc. This significantly reduces the effectiveness of the fix for monorepos that use .npmrc for other configurations (e.g., registry settings). Consider reading the file if it exists and appending the ignore-workspace=true setting if it's not already present.
…pmrc The previous "skip if .npmrc exists" guard silently bypassed the workspace fix for monorepos that already commit a root .npmrc (registry/auth/scoped configs) — exactly the repos most likely to suffer the parent-corruption bug this PR targets. Now we read the existing file and append ignore-workspace=true only when not already present, on both the backend WorktreeManager.create_worktree and the frontend createTerminalWorktree paths. Addresses CodeRabbit and Gemini Code Assist review feedback on PR AndyMik90#2016. Co-Authored-By: Octopus <liyuan851277048@icloud.com>
|
Thanks @coderabbitai and @gemini-code-assist — you all caught the same blind spot. Pushed 66747360 on both code paths to read any existing Left the duplicated logic across backend/frontend in place for this fix — they run in independent processes (Python backend + Electron main) and a shared constant module would cross several boundaries; happy to extract it in a follow-up if maintainers want. @AndyMik90 — heads up that CLA is still unsigned, can't auto-sign that from this side. |
|
Only users with a collaborator, contributor, member, or owner role can interact with CodeRabbit. |
|
Thank you for the update, @octo-patch. The approach of checking for an existing |
Fixes #2013
Problem
Auto-Claude creates worktrees inside the host repository at
.auto-claude/worktrees/tasks/<id>/and.auto-claude/worktrees/terminal/<name>/. When the host is a pnpm/npm workspace monorepo (i.e., has a rootpnpm-workspace.yamlorpackage.json#workspaces), runningpnpm installinside a worktree causes pnpm to walk up the directory tree, detect the parent workspace config as its shared workspace root, and silently re-link the parent repo'snode_modulessymlinks into the worktree's.pnpmstore.After the worktree is removed (or its
node_modulesdeleted), the parent repo'snode_modulescontains dozens of dangling symlinks pointing into a non-existent path, breaking builds with errors like:Recovery requires a full nuke + reinstall of the parent repo's dependencies.
Solution
After creating any task or terminal worktree, write a
.npmrcfile containing:This pnpm/npm setting (see pnpm docs) prevents workspace root detection from walking above the worktree directory, keeping the worktree's package-manager operations fully isolated from the host repo.
The
.npmrcis written only if one does not already exist, so projects that carry their own.npmrcare unaffected. Errors during write are logged as warnings and do not fail worktree creation.Changes:
apps/backend/core/worktree.py: inject.npmrcafter task worktree creationapps/frontend/src/main/ipc-handlers/terminal/worktree-handlers.ts: inject.npmrcafter terminal worktree creationTesting
.npmrcfile is written correctly after worktree creation via code inspection.npmrcfiles are not overwritten (guarded byif not existscheck)Summary by CodeRabbit