Skip to content

🤖 fix: prevent jsdom ENOENT from crashing Docker streaming pipeline#3025

Open
anas-domesticus wants to merge 2 commits intocoder:mainfrom
anas-domesticus:anas-domesticus/debug-web-ui-logs
Open

🤖 fix: prevent jsdom ENOENT from crashing Docker streaming pipeline#3025
anas-domesticus wants to merge 2 commits intocoder:mainfrom
anas-domesticus:anas-domesticus/debug-web-ui-logs

Conversation

@anas-domesticus
Copy link

Summary

Fix web_fetch tool crashing the entire streaming pipeline in Docker deployments. jsdom's filesystem dependency on browser/default-stylesheet.css breaks when bundled with esbuild, causing ENOENT on first message send and cascading TypeError: p is not a function on every subsequent retry.

Background

Production logs from the web UI showed two interleaved errors when sending any message:

  1. ENOENT: no such file or directory, open '/app/browser/default-stylesheet.css' — jsdom reads this CSS file from disk via a __dirname-relative path. After esbuild bundles jsdom into server-bundle.js, __dirname resolves to the bundle directory instead of jsdom's original package location, so the path is wrong.

  2. TypeError: p is not a function — after the initial ENOENT, jsdom's module is left in a partially-initialized state in Node's module cache. Every retry and fresh send re-enters the same corrupted module, hitting an undefined function (minified as p). The auto-retry manager retries every ~1 second indefinitely, flooding the logs.

The root cause is that getToolsForModel() dynamically imports web_fetch.ts (which imports jsdom) on every streamMessage call. When jsdom fails to load, the entire tool assembly throws, preventing ALL tools from being registered — not just web_fetch.

Implementation

Two layered fixes:

  1. Graceful fallback (tools.ts): Wrap the jsdom dynamic import in try/catch so web_fetch is omitted from the toolset instead of crashing. This is defense-in-depth — Anthropic models already replace web_fetch with a provider-native tool (webFetch_20250910), so losing it is low-impact.

  2. Externalize jsdom (Makefile + Dockerfile): Add --external:jsdom to ESBUILD_SERVER_FLAGS so jsdom keeps its original filesystem layout at runtime. Trace jsdom's full transitive dependency tree in the builder stage and copy it into the runtime image.

Risks

  • The jsdom dependency tracing runs require.resolve in the builder — if a transitive dep uses conditional/optional requires that aren't in dependencies, they'll be missed. The graceful fallback (fix 1) covers this as a safety net.
  • Adding jsdom's dependency tree to the Docker image increases image size. This is the correct trade-off vs. a broken web_fetch tool.

Generated with mux

anas-domesticus and others added 2 commits March 18, 2026 18:11
jsdom has filesystem dependencies (browser/default-stylesheet.css) that
break when bundled with esbuild for the Docker runtime — __dirname
resolves to the bundle location instead of jsdom's package directory.
This caused ENOENT crashes during streamMessage tool assembly, followed
by cascading "p is not a function" errors on every retry attempt as
jsdom's corrupted module cache returned undefined exports.

Wrap the dynamic import in a try/catch so the rest of the toolset still
works. Anthropic models already replace web_fetch with a provider-native
tool, so the impact is minimal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jsdom reads browser/default-stylesheet.css from disk via a
__dirname-relative path. When esbuild bundles jsdom into server-bundle.js,
__dirname resolves to the bundle directory instead of jsdom's package
directory, causing ENOENT in the Docker runtime.

Externalize jsdom from the esbuild server bundle (like ssh2 and
agent-browser) so it keeps its original filesystem layout. Add a
dependency-tracing step in the Dockerfile builder stage and copy the
full transitive tree into the runtime image.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant