Global WebSocket used on a Node 20 minimum — third independent hit today; needs a guard, not a third fix #194

Description

@pseudoseed

packages/codev/src/agent-farm/lib/tunnel-client.ts:500 uses the globalWebSocket:

ws=newWebSocket(buildTunnelWsUrl(this.options.serverUrl));

packages/codev/package.json declares engines.node: ">=20.0.0", and Node 20 has no global
WebSocket. Measured on this machine, which runs the declared minimum:

$ node -e 'console.log(process.version, typeof WebSocket)'
v20.19.2 undefined

So this line throws ReferenceError: WebSocket is not defined on the project's own minimum
supported runtime. ws: ^8.18.0 is already a dependency of packages/codev, so the fix costs
nothing new.

This is on main today and is not introduced by any open PR.

Why it is being filed now

The same bug has now been hit three times independently, by three different agents, in three
different files
in one day:

  1. spec-146-phase-9-live-harness.test.ts — surfaced the moment the test stopped being skipped
    (PR [Air #180] Pin the live t3 server runtime #191). Fixed there by using the package's existing ws.
  2. agent-farm/thread-backend.ts:91 — found by the codex lane in spec 146 phase 9's iteration-2
    review. Being fixed on that branch.
  3. This one, found by sweeping for the pattern after the second hit.

Three independent discoveries of one root cause is the signal that the pattern needs a guard,
not three fixes. Nothing currently stops a fourth.

Ask

  1. Replace the global with the ws package here.
  2. Add a lint rule or a test that fails on new WebSocket( outside a ws import, across
    packages/*/src. A grep-based test in the style of the existing boundary tests is enough —
    the point is that the next occurrence is caught by CI rather than by whichever agent happens
    to run on Node 20.
  3. Any such test must assert its own reach: it should fail if pointed at a directory that does
    not exist, so a path typo cannot make it vacuously green.

Note the asymmetry that makes this easy to miss: on Node 22+ every one of these works fine. The
failure is invisible to anyone not running the declared minimum, which is exactly the runtime the
project promises to support.

Related: #190, #193 (guards that cannot report their own reach).

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

    area/coreCore libraries and shared logic

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); })();
      Skip to content

      Global WebSocket used on a Node 20 minimum — third independent hit today; needs a guard, not a third fix #194

      Description

      @pseudoseed

      packages/codev/src/agent-farm/lib/tunnel-client.ts:500 uses the globalWebSocket:

      ws=newWebSocket(buildTunnelWsUrl(this.options.serverUrl));

      packages/codev/package.json declares engines.node: ">=20.0.0", and Node 20 has no global
      WebSocket. Measured on this machine, which runs the declared minimum:

      $ node -e 'console.log(process.version, typeof WebSocket)'
      v20.19.2 undefined
      

      So this line throws ReferenceError: WebSocket is not defined on the project's own minimum
      supported runtime. ws: ^8.18.0 is already a dependency of packages/codev, so the fix costs
      nothing new.

      This is on main today and is not introduced by any open PR.

      Why it is being filed now

      The same bug has now been hit three times independently, by three different agents, in three
      different files
      in one day:

      1. spec-146-phase-9-live-harness.test.ts — surfaced the moment the test stopped being skipped
        (PR [Air #180] Pin the live t3 server runtime #191). Fixed there by using the package's existing ws.
      2. agent-farm/thread-backend.ts:91 — found by the codex lane in spec 146 phase 9's iteration-2
        review. Being fixed on that branch.
      3. This one, found by sweeping for the pattern after the second hit.

      Three independent discoveries of one root cause is the signal that the pattern needs a guard,
      not three fixes. Nothing currently stops a fourth.

      Ask

      1. Replace the global with the ws package here.
      2. Add a lint rule or a test that fails on new WebSocket( outside a ws import, across
        packages/*/src. A grep-based test in the style of the existing boundary tests is enough —
        the point is that the next occurrence is caught by CI rather than by whichever agent happens
        to run on Node 20.
      3. Any such test must assert its own reach: it should fail if pointed at a directory that does
        not exist, so a path typo cannot make it vacuously green.

      Note the asymmetry that makes this easy to miss: on Node 22+ every one of these works fine. The
      failure is invisible to anyone not running the declared minimum, which is exactly the runtime the
      project promises to support.

      Related: #190, #193 (guards that cannot report their own reach).

      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

        area/coreCore libraries and shared logic

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions