Skip to content

gjc --worktree shares the origin checkout's node_modules, contaminating worktree module resolution #4620

Description

@Yeachan-Heo

Problem

ensureReusableNodeModules (packages/coding-agent/src/gjc-runtime/launch-worktree.ts:453) symlinks the entire node_modules from the origin checkout into a freshly created launch worktree:

export function ensureReusableNodeModules(sourceRoot: string, worktreePath: string): "symlink" | "present" | "missing" {
	const target = path.join(worktreePath, "node_modules");
	if (fs.existsSync(target)) return "present";
	const source = path.join(sourceRoot, "node_modules");
	if (!fs.existsSync(source)) return "missing";
	fs.symlinkSync(source, target, "junction");
	return "symlink";
}

Because Bun workspace installs link @gajae-code/* packages back to the origin checkout's packages/*, every workspace import in the worktree resolves to the origin checkout's live sources, not the worktree's own commit. Two concrete failure classes observed while verifying the 0.14.0 release worktree (frozen head c83ffe3d7f):

  1. Cannot find module '@gajae-code/ai/utils/json-parse' from '/Users/.../gajae-code/packages/agent/src/proxy.ts' — the main checkout's proxy.ts was pulled into the worktree's test run, and its import graph disagreed with the worktree's dependency state. Any divergence between the two checkouts (different branch, mid-rebase, uncommitted edits) produces arbitrary, hard-to-attribute failures.
  2. Worse, mutations flow backwards: running bun install inside the worktree (after deleting a broken package link) rewrote the origin checkout's node_modules workspace links to point into the worktree. Deleting the worktree afterwards would strand the origin checkout with dangling links.

Non-workspace deps also break: the shared node_modules may not satisfy the worktree branch's bun.lock (new/updated deps on the worktree branch are simply missing), and compiled native addon state (packages/natives/native/*.node) is per-checkout, so worktree natives resolve to the origin checkout's build (or its absence).

Impact

  • Test/verification results obtained inside a gjc --worktree tree are not trustworthy for the worktree's commit — they exercise a hybrid of two checkouts.
  • Worktree-side installs corrupt the origin checkout's node_modules.
  • This monorepo itself already documents the symptom class: natives-augment.d.ts notes "Linked sibling checkouts sharing the root node_modules can dedupe @gajae-code/natives declarations by package ID to an older generated file".

Suggested direction

For workspace repos (root package.json with workspaces), do not share node_modules; run a real bun install --frozen-lockfile in the worktree (or at minimum copy the lockfile and install). The symlink reuse is only safe for repos whose node_modules contains no links back into the repo itself. A cheap heuristic: share only when the origin node_modules contains no symlink whose target resolves inside sourceRoot.

Environment

  • gjc dev @ c83ffe3d7f (release/0.14.0 freeze), Bun 1.3.14, macOS arm64

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions