Skip to content

fix(core): separate daemon runtime env from graph identity - #36565

Draft
sdjayna wants to merge 2 commits into
nrwl:masterfrom
sdjayna:fix/berry-daemon-graph-identity
Draft

fix(core): separate daemon runtime env from graph identity#36565
sdjayna wants to merge 2 commits into
nrwl:masterfrom
sdjayna:fix/berry-daemon-graph-identity

Conversation

@sdjayna

@sdjayna sdjayna commented Aug 4, 2026

Copy link
Copy Markdown

Current Behavior

“Berry” is Yarn's name for Yarn 2 and later; this reproduction uses Yarn 4.15.0. When Yarn runs a workspace binary, it does not invoke the binary directly. It creates a fresh temporary xfs-* directory for that invocation and writes wrappers into it for the selected node, yarn, node-gyp, and package binaries.

Yarn then exposes that runtime directory and puts it first on PATH:

BERRY_BIN_FOLDER = /tmp/.../xfs-0a01a6bc
PATH              = <BERRY_BIN_FOLDER><delimiter><existing toolchain PATH>

The first PATH entry is exactly the current BERRY_BIN_FOLDER value. The delimiter is : on macOS/Linux and ; on Windows; every remaining PATH entry is the pre-existing toolchain path.

The directory is deleted when the Yarn command exits. A new command receives a new random path even when the workspace, lockfile, Node version, Yarn version, and available binaries are unchanged.

Two real consecutive invocations demonstrated the distinction:

Invocation 1: .../xfs-0a01a6bc
Invocation 2: .../xfs-44f3d452

After removing that first temporary segment, the complete remaining PATH was byte-for-byte identical. Both directories contained the same 73 wrapper names with identical SHA-256 fingerprints. The path changed; the executable environment it represented did not.

The Nx client sends its environment snapshot to the persistent daemon. The daemon currently applies the raw values and invalidates the project graph if any non-excluded value differs:

const changedEnvKeys = applyDaemonEnvFromClient(payload.env);
if (changedEnvKeys.length > 0) {
  forwardEnvToPluginWorkers(payload.env);
  invalidateGraphCache();
}

Every yarn nx call therefore changes both BERRY_BIN_FOLDER and the first PATH segment. Nx treats those random runtime locations as changes to project-graph identity and rebuilds an unchanged graph. The public reproduction logs this before all three requests on Windows, macOS, and Ubuntu:

Graph recompute necessary due to env variable refresh.
Changed keys: PATH, BERRY_BIN_FOLDER

Expected Behavior

Nx needs two views of the same client environment:

View Required semantics
Runtime environment Forward the complete latest environment, including the current real BERRY_BIN_FOLDER, so plugin workers can execute Yarn-provided wrappers
Graph identity Compare stable inputs that can change projects, targets, dependencies, SDK selection, or compiler behavior

For graph comparison only, Nx should:

  1. Replace the random BERRY_BIN_FOLDER value with a stable Yarn marker.
  2. Remove only the PATH segment exactly equal to that temporary folder.
  3. Preserve every remaining PATH segment and SDK/compiler variable.

This keeps consecutive Yarn invocations equivalent without collapsing Yarn and direct invocation into the same identity. A direct invocation has no Yarn marker. A real .NET, Java, Node, or compiler path change remains visible and must still invalidate the graph.

The full runtime environment is still applied and forwarded on every request; normalization affects cache-validity comparison only.

Before / After Acceptance

The before values are measured on stock Nx 23.1.1. The after values are acceptance targets, not patched results yet.

Metric Before: stock Nx, measured After: patched acceptance Status
Large-workspace unchanged warm command 10.030-16.543s <2.0s Awaiting dedicated PR release on the same workspace
Berry-only graph invalidations 3/3 requests on Windows, macOS and Ubuntu 0/3 requests Unit behavior passes; daemon e2e pending
Real SDK PATH invalidation Invalidates Still invalidates Focused unit test passes
Graph output 1,116 projects in large workspace Identical projects, targets and dependencies Before/after graph diff pending

For context, when the large-workspace graph was actually reused outside the Yarn-triggered invalidation path, three requests completed in 266-270ms, a 37-62x difference from the repeated 10.030-16.543s path. This is evidence of the available cache-hit path, not a claimed patched result.

No annual productivity saving is claimed without command-frequency and developer-population data.

What This PR Changes

  • Separates runtime-changed keys from graph-identity-changed keys.
  • Always forwards the latest runtime environment to plugin workers.
  • Replaces the random BERRY_BIN_FOLDER value with a stable Yarn marker and removes only matching temporary PATH segments from graph comparison.
  • Keeps PROJECT_CWD, INIT_CWD, all real PATH segments and SDK variables graph-significant.
  • Handles Windows case-insensitive and separator-normalized exact matching.
  • Adds focused unit coverage for Berry churn, exact segment removal, runtime forwarding and real toolchain changes.

Testing

Completed locally:

  • Focused Jest suite: 8 passed, 0 failed.
  • Verified two Berry runtime folders produce equal graph identity.
  • Verified the second Berry folder is applied to runtime environment.
  • Verified Yarn and direct invocation remain distinct graph identities.
  • Verified a real .NET SDK PATH change remains graph-significant.
  • Verified Windows path case/separator handling.
  • Public stock-Nx workflow (https://github.com/sdjayna/nx-daemon-reproductions/actions/runs/30946807413) reproduced Berry-triggered invalidation on Windows, macOS and Ubuntu.

Required in CI / dedicated PR release:

  • Run Nx unit and daemon e2e suites.
  • Verify a Yarn-provided plugin binary executes during request-driven graph construction.
  • Verify watcher-driven background recomputation after Yarn removes its temporary folder.
  • Compare project names, targets and dependencies before/after in a large Yarn workspace.

Risk and Mitigation

This PR does not broadly ignore PATH and does not remove Berry wrappers from runtime. Only the exact path segment equal to BERRY_BIN_FOLDER is excluded from graph identity. Lockfile and project-file changes continue to invalidate through file watching.

Related Issue(s)

Fixes #36564

This change is a candidate for a dedicated PR release so graph output and warm-command latency can be compared in a large Yarn 4 workspace.

@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

👷 Deploy request for nx-docs pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit fd477cd

@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

👷 Deploy request for nx-dev pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit fd477cd

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Yarn Berry temporary PATH invalidates an unchanged daemon graph

1 participant