Skip to content

Conversation

@MaxKless
Copy link
Collaborator

  • basic start: remove old watchers & show warning
  • also mention daemon not running in error
  • implement alongside other listeners
  • emit event when watcher gives up
  • restart daemon watcher from button
  • small fixes

@nx-cloud
Copy link
Contributor

nx-cloud bot commented Oct 29, 2025

View your CI Pipeline Execution ↗ for commit c062131

Command Status Duration Result
nx-cloud record -- yarn nx format:check --verbose ❌ Failed 7s View ↗

☁️ Nx Cloud last updated this comment at 2025-10-29 16:13:15 UTC

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.

Nx Cloud is proposing a fix for your failed CI:

These changes fix the format:check failure by applying Prettier formatting to the newly added passive-daemon-watcher.spec.ts file. The format command automatically corrected the formatting issues in the test file to comply with the project's Prettier configuration.

We verified this fix by re-running nx-cloud record -- yarn nx format:check --verbose.

Suggested Fix changes
diff --git a/libs/shared/watcher/src/lib/passive-daemon-watcher.spec.ts b/libs/shared/watcher/src/lib/passive-daemon-watcher.spec.ts
index 0db0b901..c7b5dbe6 100644
--- a/libs/shared/watcher/src/lib/passive-daemon-watcher.spec.ts
+++ b/libs/shared/watcher/src/lib/passive-daemon-watcher.spec.ts
@@ -110,239 +110,207 @@ describe('PassiveDaemonWatcher', () => {
   });
 
   describe('Retry on Registration Failure', () => {
-    it(
-      'should retry registration if getNxDaemonClient fails',
-      async () => {
-        (getNxDaemonClient as jest.Mock)
-          .mockRejectedValueOnce(new Error('Failed'))
-          .mockRejectedValueOnce(new Error('Failed'))
-          .mockResolvedValueOnce({ daemonClient: mockDaemonClient });
+    it('should retry registration if getNxDaemonClient fails', async () => {
+      (getNxDaemonClient as jest.Mock)
+        .mockRejectedValueOnce(new Error('Failed'))
+        .mockRejectedValueOnce(new Error('Failed'))
+        .mockResolvedValueOnce({ daemonClient: mockDaemonClient });
 
-        const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
-        const listener = jest.fn();
-
-        watcher.listen(listener);
-        watcher.start();
+      const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+      const listener = jest.fn();
 
-        await flushPromises();
+      watcher.listen(listener);
+      watcher.start();
 
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(1);
+      await flushPromises();
 
-        await new Promise((resolve) => setTimeout(resolve, 2500));
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(1);
 
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(2);
+      await new Promise((resolve) => setTimeout(resolve, 2500));
 
-        await new Promise((resolve) => setTimeout(resolve, 5500));
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(2);
 
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(3);
+      await new Promise((resolve) => setTimeout(resolve, 5500));
 
-        const mockData = {
-          projectGraph: { nodes: {}, dependencies: {} },
-          sourceMaps: {},
-        };
-        capturedDaemonCallback(null, mockData);
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(3);
 
-        expect(listener).toHaveBeenCalledWith(null, mockData);
+      const mockData = {
+        projectGraph: { nodes: {}, dependencies: {} },
+        sourceMaps: {},
+      };
+      capturedDaemonCallback(null, mockData);
 
-        watcher.dispose();
-      },
-      15000,
-    );
+      expect(listener).toHaveBeenCalledWith(null, mockData);
 
-    it(
-      'should retry registration if daemon.enabled() returns false',
-      async () => {
-        mockDaemonClient.enabled
-          .mockReturnValueOnce(false)
-          .mockReturnValueOnce(true);
+      watcher.dispose();
+    }, 15000);
 
-        const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
-        watcher.start();
+    it('should retry registration if daemon.enabled() returns false', async () => {
+      mockDaemonClient.enabled
+        .mockReturnValueOnce(false)
+        .mockReturnValueOnce(true);
 
-        await flushPromises();
+      const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+      watcher.start();
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(0);
+      await flushPromises();
 
-        await new Promise((resolve) => setTimeout(resolve, 2500));
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(0);
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(1);
+      await new Promise((resolve) => setTimeout(resolve, 2500));
 
-        watcher.dispose();
-      },
-      10000,
-    );
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(1);
 
-    it(
-      'should retry registration if registerProjectGraphRecomputationListener throws',
-      async () => {
-        mockDaemonClient.registerProjectGraphRecomputationListener
-          .mockImplementationOnce(() => {
-            throw new Error('Registration failed');
-          })
-          .mockImplementationOnce((callback: any) => {
-            capturedDaemonCallback = callback;
-            return mockUnregister;
-          });
+      watcher.dispose();
+    }, 10000);
+
+    it('should retry registration if registerProjectGraphRecomputationListener throws', async () => {
+      mockDaemonClient.registerProjectGraphRecomputationListener
+        .mockImplementationOnce(() => {
+          throw new Error('Registration failed');
+        })
+        .mockImplementationOnce((callback: any) => {
+          capturedDaemonCallback = callback;
+          return mockUnregister;
+        });
 
-        const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
-        watcher.start();
+      const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+      watcher.start();
 
-        await flushPromises();
+      await flushPromises();
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(1);
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(1);
 
-        await new Promise((resolve) => setTimeout(resolve, 2500));
+      await new Promise((resolve) => setTimeout(resolve, 2500));
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(2);
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(2);
 
-        watcher.dispose();
-      },
-      10000,
-    );
+      watcher.dispose();
+    }, 10000);
   });
 
   describe('Retry on Listener Error', () => {
-    it(
-      'should retry when daemon listener callback receives an error',
-      async () => {
-        const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
-        watcher.start();
+    it('should retry when daemon listener callback receives an error', async () => {
+      const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+      watcher.start();
 
-        await flushPromises();
+      await flushPromises();
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(1);
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(1);
 
-        capturedDaemonCallback(new Error('Daemon error'));
+      capturedDaemonCallback(new Error('Daemon error'));
 
-        await new Promise((resolve) => setTimeout(resolve, 2500));
+      await new Promise((resolve) => setTimeout(resolve, 2500));
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(2);
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(2);
 
-        watcher.dispose();
-      },
-      10000,
-    );
+      watcher.dispose();
+    }, 10000);
 
-    it(
-      'should retry when daemon listener callback receives "closed"',
-      async () => {
-        const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
-        watcher.start();
+    it('should retry when daemon listener callback receives "closed"', async () => {
+      const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+      watcher.start();
 
-        await flushPromises();
+      await flushPromises();
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(1);
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(1);
 
-        capturedDaemonCallback('closed');
+      capturedDaemonCallback('closed');
 
-        await new Promise((resolve) => setTimeout(resolve, 2500));
+      await new Promise((resolve) => setTimeout(resolve, 2500));
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(2);
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(2);
 
-        watcher.dispose();
-      },
-      10000,
-    );
+      watcher.dispose();
+    }, 10000);
 
-    it(
-      'should notify listeners of error before retrying',
-      async () => {
-        const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
-        const listener = jest.fn();
+    it('should notify listeners of error before retrying', async () => {
+      const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+      const listener = jest.fn();
 
-        watcher.listen(listener);
-        watcher.start();
+      watcher.listen(listener);
+      watcher.start();
 
-        await flushPromises();
+      await flushPromises();
 
-        const error = new Error('Daemon error');
-        capturedDaemonCallback(error);
+      const error = new Error('Daemon error');
+      capturedDaemonCallback(error);
 
-        expect(listener).toHaveBeenCalledWith(error);
+      expect(listener).toHaveBeenCalledWith(error);
 
-        await new Promise((resolve) => setTimeout(resolve, 2500));
+      await new Promise((resolve) => setTimeout(resolve, 2500));
 
-        expect(
-          mockDaemonClient.registerProjectGraphRecomputationListener,
-        ).toHaveBeenCalledTimes(2);
+      expect(
+        mockDaemonClient.registerProjectGraphRecomputationListener,
+      ).toHaveBeenCalledTimes(2);
 
-        watcher.dispose();
-      },
-      10000,
-    );
+      watcher.dispose();
+    }, 10000);
   });
 
   describe('Exponential Backoff', () => {
-    it(
-      'should use exponential backoff for retries (2s, 5s, 10s, 20s)',
-      async () => {
-        (getNxDaemonClient as jest.Mock).mockRejectedValue(
-          new Error('Always fails'),
-        );
+    it('should use exponential backoff for retries (2s, 5s, 10s, 20s)', async () => {
+      (getNxDaemonClient as jest.Mock).mockRejectedValue(
+        new Error('Always fails'),
+      );
 
-        const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
-        watcher.start();
+      const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+      watcher.start();
 
-        await flushPromises();
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(1);
+      await flushPromises();
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(1);
 
-        await new Promise((resolve) => setTimeout(resolve, 2500));
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(2);
+      await new Promise((resolve) => setTimeout(resolve, 2500));
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(2);
 
-        await new Promise((resolve) => setTimeout(resolve, 5500));
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(3);
+      await new Promise((resolve) => setTimeout(resolve, 5500));
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(3);
 
-        await new Promise((resolve) => setTimeout(resolve, 10500));
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(4);
+      await new Promise((resolve) => setTimeout(resolve, 10500));
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(4);
 
-        await new Promise((resolve) => setTimeout(resolve, 20500));
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
+      await new Promise((resolve) => setTimeout(resolve, 20500));
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
 
-        watcher.dispose();
-      },
-      50000,
-    );
+      watcher.dispose();
+    }, 50000);
 
-    it(
-      'should stop retrying after 4 failed attempts',
-      async () => {
-        (getNxDaemonClient as jest.Mock).mockRejectedValue(
-          new Error('Always fails'),
-        );
+    it('should stop retrying after 4 failed attempts', async () => {
+      (getNxDaemonClient as jest.Mock).mockRejectedValue(
+        new Error('Always fails'),
+      );
 
-        const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
-        watcher.start();
+      const watcher = new PassiveDaemonWatcher('/workspace', mockLogger);
+      watcher.start();
 
-        await flushPromises();
+      await flushPromises();
 
-        await new Promise((resolve) => setTimeout(resolve, 40000));
+      await new Promise((resolve) => setTimeout(resolve, 40000));
 
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
 
-        await new Promise((resolve) => setTimeout(resolve, 5000));
+      await new Promise((resolve) => setTimeout(resolve, 5000));
 
-        expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
+      expect(getNxDaemonClient).toHaveBeenCalledTimes(5);
 
-        watcher.dispose();
-      },
-      50000,
-    );
+      watcher.dispose();
+    }, 50000);
   });
 
   describe('Cleanup', () => {
@@ -454,35 +422,31 @@ describe('PassiveDaemonWatcher', () => {
       watcher.dispose();
     });
 
-    it(
-      'should call callback with false when permanently failed',
-      async () => {
-        const onOperationalStateChange = jest.fn();
-        (getNxDaemonClient as jest.Mock).mockRejectedValue(
-          new Error('Always fails'),
-        );
-
-        const watcher = new PassiveDaemonWatcher(
-          '/workspace',
-          mockLogger,
-          onOperationalStateChange,
-        );
-
-        watcher.start();
-        await flushPromises();
-
-        await new Promise((resolve) => setTimeout(resolve, 45000));
-        await flushPromises();
-
-        const falseCalls = onOperationalStateChange.mock.calls.filter(
-          (call) => call[0] === false,
-        );
-        expect(falseCalls.length).toBeGreaterThan(0);
-
-        watcher.dispose();
-      },
-      50000,
-    );
+    it('should call callback with false when permanently failed', async () => {
+      const onOperationalStateChange = jest.fn();
+      (getNxDaemonClient as jest.Mock).mockRejectedValue(
+        new Error('Always fails'),
+      );
+
+      const watcher = new PassiveDaemonWatcher(
+        '/workspace',
+        mockLogger,
+        onOperationalStateChange,
+      );
+
+      watcher.start();
+      await flushPromises();
+
+      await new Promise((resolve) => setTimeout(resolve, 45000));
+      await flushPromises();
+
+      const falseCalls = onOperationalStateChange.mock.calls.filter(
+        (call) => call[0] === false,
+      );
+      expect(falseCalls.length).toBeGreaterThan(0);
+
+      watcher.dispose();
+    }, 50000);
 
     it('should call callback with true after stop', async () => {
       const onOperationalStateChange = jest.fn();

Apply fix via Nx Cloud  Reject fix via Nx Cloud

Or Apply changes locally with:

npx nx-cloud apply-locally ZpZ6-b7po

Apply fix locally with your editor ↗  View interactive diff ↗


🎓 To learn more about Self Healing CI, please visit nx.dev

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.

2 participants