Skip to content

Conversation

@FrozenPandaz
Copy link
Collaborator

@FrozenPandaz FrozenPandaz commented Nov 22, 2025

Current Behavior

  1. Socket Race Condition: All daemon servers listen on the same socket path, causing a race condition where shutting down daemons remove sockets that newly started
    daemons are listening on.

  2. Daemon Console Check Blocks: The daemon availability check runs synchronously and blocks the main thread.

  3. Version Mismatch Issues: Packages using a different nx version than what's installed in the workspace could still use the daemon, leading to potential issues.

Expected Behavior

  1. Each daemon server creates a unique socket path based on its process ID, preventing race conditions.

  2. The daemon console check runs in the background without blocking.

  3. The daemon is disabled when there's a version mismatch between the running nx and the workspace's installed version.

Changes

1. Unique Daemon Socket Paths

  • Include process.pid in the socket directory hash to make each daemon's path unique
  • Store the socket path in server-process.json so clients know where to connect
  • Clients read the socket path from the file instead of calculating it

2. Backgroundable Daemon Check

3. Version Mismatch Check

  • Added isNxVersionMismatch() check in DaemonClient.enabled()
  • Created shared utility is-nx-version-mismatch.ts for version comparison
  • Refactored server.ts to use the shared utility
  • Uses require.resolve('nx/package.json', { paths: [workspaceRoot] }) to properly resolve the workspace's installed nx version

Related Issue(s)

Fixes daemon socket path race condition and improves daemon reliability.

@FrozenPandaz FrozenPandaz requested a review from a team as a code owner November 22, 2025 18:19
@vercel
Copy link

vercel bot commented Nov 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
nx-dev Ready Ready Preview Nov 24, 2025 11:11pm

@netlify
Copy link

netlify bot commented Nov 22, 2025

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit c462b75
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6924e52db0e0370008d7e107
😎 Deploy Preview https://deploy-preview-33580--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud
Copy link
Contributor

nx-cloud bot commented Nov 22, 2025

View your CI Pipeline Execution ↗ for commit c462b75

Command Status Duration Result
nx affected --targets=lint,test,test-kt,build,e... ✅ Succeeded 33m 23s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 2m 44s View ↗
nx-cloud record -- nx-cloud conformance:check ✅ Succeeded 11s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 2s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2025-11-24 23:45:20 UTC

Comment on lines +431 to +434
await execAsync(preInstallCommand, execOptions);
}

await execAsync(installCommand, execOptions);
Copy link
Contributor

Choose a reason for hiding this comment

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

The execAsync (promisified exec) does not support the stdio option that exists in execOptions. The exec function only supports options like encoding, timeout, maxBuffer, etc., while stdio is only available for execSync and spawn. This will cause the stdio configuration to be silently ignored, potentially causing output to appear when it shouldn't or vice versa.

// Fix: Use separate options for async execution
const execAsyncOptions = {
  cwd: tempDir,
  windowsHide: false,
};

if (preInstallCommand) {
  await execAsync(preInstallCommand, execAsyncOptions);
}

await execAsync(installCommand, execAsyncOptions);

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@github-actions
Copy link
Contributor

🐳 We have a release for that!

This PR has a release associated with it. You can try it out using this command:

npx [email protected] my-workspace

Or just copy this version and use it in your own command:

0.0.0-pr-33580-5c615b0
Release details 📑
Published version 0.0.0-pr-33580-5c615b0
Triggered by @FrozenPandaz
Branch daemon-fix2
Commit 5c615b0
Workflow run 19638741909

To request a new release for this pull request, mention someone from the Nx team or the @nrwl/nx-pipelines-reviewers.

Comment on lines 322 to 327
await this.queue.sendToQueue(async () => {
this.startDaemonIfNecessary();

const socketPath = this.getSocketPath();

messenger = new DaemonSocketMessenger(connect(socketPath)).listen(
Copy link
Contributor

Choose a reason for hiding this comment

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

Critical race condition: startDaemonIfNecessary() is async but not awaited. This will cause the code to proceed to get the socket path before the daemon has finished starting and writing the socket path to the cache file.

Fix:

await this.queue.sendToQueue(async () => {
  await this.startDaemonIfNecessary();

  const socketPath = this.getSocketPath();

  messenger = new DaemonSocketMessenger(connect(socketPath)).listen(
Suggested change
await this.queue.sendToQueue(async () => {
this.startDaemonIfNecessary();
const socketPath = this.getSocketPath();
messenger = new DaemonSocketMessenger(connect(socketPath)).listen(
await this.queue.sendToQueue(async () => {
await this.startDaemonIfNecessary();
const socketPath = this.getSocketPath();
messenger = new DaemonSocketMessenger(connect(socketPath)).listen(

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

nx-cloud[bot]

This comment was marked as outdated.

Copy link
Contributor

@nx-cloud nx-cloud bot left a comment

Choose a reason for hiding this comment

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

Important

A new CI pipeline execution was requested that may update the conclusion below...

Nx Cloud is proposing a fix for your failed CI:

These changes fix the daemon startup failures by resolving a circular dependency in isServerAvailable(). The method was calling getSocketPath() which required the daemon cache to exist, but during startup the daemon hadn't written its socket path yet, causing ENOENT errors. By having isServerAvailable() directly read from the cache and return false when no socket path exists, the daemon can now complete its startup sequence successfully.

diff --git a/packages/nx/src/daemon/client/client.ts b/packages/nx/src/daemon/client/client.ts
index 65f4013e96..2c3b1e4ed9 100644
--- a/packages/nx/src/daemon/client/client.ts
+++ b/packages/nx/src/daemon/client/client.ts
@@ -592,11 +592,19 @@ export class DaemonClient {
   async isServerAvailable(): Promise<boolean> {
     return new Promise((resolve) => {
       try {
-        const socketPath = this.getSocketPath();
-        if (!socketPath) {
+        // Try to get socket path from cache first (for existing daemon)
+        const daemonProcessJson = readDaemonProcessJsonCache();
+        let socketPath: string;
+
+        if (daemonProcessJson?.socketPath) {
+          socketPath = daemonProcessJson.socketPath;
+        } else {
+          // Daemon hasn't started yet or no cache exists
+          // This can happen when checking availability during startup
           resolve(false);
           return;
         }
+
         const socket = connect(socketPath, () => {
           socket.destroy();
           resolve(true);

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally fbRU-Bob2

Apply fix locally with your editor ↗   View interactive diff ↗


🎓 Learn more about Self-Healing CI on nx.dev

… condition

Each daemon server now creates a socket path that includes its PID in the hash, preventing a race condition where an old daemon shutting down could remove a newly started daemon's socket file.

Changes:
- Include process.pid in socket directory hash
- Store socket path in server-process.json
- Client reads socket path from server-process.json
- Ensure daemon is started if no socket path exists

This prevents the cycle where daemons continuously start and shut down due to socket path conflicts.
export function getInstalledNxVersion(): string | null {
try {
const nxPackageJsonPath = require.resolve('nx/package.json', {
paths: [workspaceRoot],
Copy link
Member

Choose a reason for hiding this comment

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

Use require paths util

@FrozenPandaz FrozenPandaz enabled auto-merge (squash) November 24, 2025 23:13
@FrozenPandaz FrozenPandaz merged commit f31bef7 into master Nov 24, 2025
20 checks passed
@FrozenPandaz FrozenPandaz deleted the daemon-fix2 branch November 24, 2025 23:45
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.

3 participants