Skip to content

chore(web): silence benign config-load dep warnings in npm run dev - #1408

Merged
cliffhall merged 2 commits into
v2/mainfrom
v2/quiet-dev-config-warnings
Jun 1, 2026
Merged

cliffhall merged 2 commits into
v2/mainfrom
v2/quiet-dev-config-warnings

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Problem

npm run dev (vite) printed three UNRESOLVED_IMPORT warnings at startup, before the server banner:

core/mcp/remote/node/server.ts … Could not resolve 'chokidar' … treating it as an external dependency
core/storage/store-io.ts … Could not resolve 'atomically' …
core/auth/node/secret-store.ts … Could not resolve '@napi-rs/keyring' …

Why they happen

The Hono dev plugin statically imports the node-only backend (core/mcp/remote/node/server.ts). Loading vite.config.ts therefore pulls that file into Vite's config bundler (Rolldown), which walks its node-only deps and warns that it's externalizing them. They're benign — the browser never loads these deps, and Node resolves them at runtime.

Why the usual fixes don't work

  • The static import can't be swapped for ssrLoadModule or --configLoader runner: core/ has no node_modules, so the aliases must resolve these deps at bundle time. Both alternatives fail with Cannot find module 'atomically' / require is not defined.
  • The warnings are written directly to the process streams during config loading, before any Vite logger exists — so customLogger, --logLevel silent, and build.rollupOptions.onwarn all miss them (verified).

Fix

A small --import hook (server/quiet-config-warnings.mjs) that drops only those specific benign warning writes (matched by UNRESOLVED_IMPORT + the exact package/source-file pair), wired into the dev script:

"dev": "node --import ./server/quiet-config-warnings.mjs ./node_modules/vite/bin/vite.js"

Scoped to npm run dev only — build, tests, Storybook, and prod are untouched. All other dev output (errors, HMR, real warnings) passes through unchanged.

Verification

npm run dev startup is now clean (0 of the 3 warnings, no code-frame remnants, banner/sandbox URL intact). npm run validate green.

🤖 Generated with Claude Code

`vite dev` printed three UNRESOLVED_IMPORT warnings at startup (chokidar,
atomically, @napi-rs/keyring). They originate from Vite's config bundler
(Rolldown): the Hono plugin statically imports the node-only dev backend
(core/mcp/remote/node/server.ts), so loading vite.config.ts bundles it and
Rolldown walks its node-only deps. Those deps are correctly externalized — the
browser never loads them and Node resolves them at runtime — so the warnings are
pure noise.

They can't be filtered the usual ways: the static import is required (the
monorepo has no node_modules under core/, so the aliases must resolve the deps
at bundle time — ssrLoadModule / `--configLoader runner` both fail to resolve
them), and the warnings are written directly to the process streams during
config load, bypassing customLogger, logLevel, and rollupOptions.onwarn.

Fix: a tiny `--import` hook (server/quiet-config-warnings.mjs) that drops only
those specific benign warning writes, wired into the `dev` script. Scoped to
`npm run dev` — build, tests, and prod are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Jun 1, 2026
@cliffhall

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 1m 33s —— View job


Claude PR Review

  • Read the changed files
  • Check verification of the warning-match logic
  • Assess alternative approaches discussion
  • Post review feedback

Summary

Tight, well-scoped fix for a real annoyance. The unusual approach (monkey-patching process.{stdout,stderr}.write) is well-justified by the constraint analysis in the file header: the warnings are emitted directly to streams during config load, before any Vite logger exists, so no Vite-level hook can suppress them. The PR limits the blast radius to npm run dev only and filters with a specific UNRESOLVED_IMPORT + (package, source-file) signature, so real warnings still pass through.

Strengths

  • Excellent rationale documentation. The file header (quiet-config-warnings.mjs:1-17) explains why hooks/loggers can't catch this and why a stream filter is necessary. The PR description further rules out ssrLoadModule / --configLoader runner. This is the kind of doc you actually want next to a monkey-patch.
  • Scope is correct. Only dev is patched (package.json:7); build, tests, Storybook, and prod use unmodified Vite. The patch lives in --import and exits with the process.
  • Write semantics are correct. Returning true and invoking the encoding/callback (quiet-config-warnings.mjs:42-45) preserves the stream contract — callers and drain listeners won't stall.
  • Handles both overloads of stream.write(chunk, callback) and stream.write(chunk, encoding, callback) (lines 43-44).
  • Filter specificity. Requiring both UNRESOLVED_IMPORT and the (dep, file) pair (lines 25-30) makes accidental over-suppression unlikely.

Suggestions (optional, not blocking)

  1. Tighten the filename match to a path fragment. server.ts and store-io.ts are generic-enough names that, in theory, an unrelated future UNRESOLVED_IMPORT warning mentioning chokidar and a file named server.ts could be silently dropped. The probability is low, but anchoring on a deeper fragment is cheap insurance:

    const BENIGN_PAIRS = [
      ["'chokidar'", "core/mcp/remote/node/server.ts"],
      ["'atomically'", "core/storage/store-io.ts"],
      ["'@napi-rs/keyring'", "core/auth/node/secret-store.ts"],
    ];

    Worth a quick check that Rolldown still prints the full repo-relative path (it normally does). If it only prints the basename in some cases, keep the current matcher. Fix this →

  2. Drift risk worth noting. The matcher is tied to Rolldown's current warning format. If they change the quote style ('chokidar' → "chokidar") or restructure the message, the filter silently degrades to a no-op and the warnings return. That's the right failure mode (warnings come back, nothing breaks) — but a one-line comment in the file noting "if these warnings reappear, check whether Rolldown changed its message format" might save a future reader 10 minutes. Optional.

  3. No tests. AGENTS.md asks for tests on new code, and this file isn't covered. I think this is a reasonable exception (dev-only utility, monkey-patches process streams, fastest verification is npm run dev), but a quick unit test for isBenignWarning (pure function — takes a string, returns a boolean) would be cheap and would lock in the signature pairs. Skip if you'd rather not.

Non-issues I considered

  • Uint8Array chunks fall through to the original write (since text ends up "" and the benign check is false-y), which is the safe default.
  • stream.writev isn't patched, but Rolldown writes the warning as a single chunk, so this isn't a real concern.
  • Hardcoded ./node_modules/vite/bin/vite.js is fine — clients/web has its own node_modules with vite as a direct dep, and npm runs scripts from the package dir.

Verdict

LGTM. Ship as-is, or take suggestion (1) if you want a little extra safety margin. The AGENTS.md update at AGENTS.md:19-20 is also good — it accurately describes the new file's purpose and scope.
• v2/quiet-dev-config-warnings

- Tighten BENIGN_PAIRS to repo-relative path fragments (not basenames) so an
  unrelated future UNRESOLVED_IMPORT warning mentioning the same dep + a
  same-named file isn't silently dropped. Verified Rolldown prints the full
  repo-relative path. (review #1)
- Add a note that the matcher tracks Rolldown's message format and degrades to a
  safe no-op (warnings return) if that format changes. (review #2)
- Export isBenignWarning/BENIGN_PAIRS, guard the stream patching behind
  !VITEST, add a .d.mts so the pure helper is importable from TS, and add a unit
  test locking the signature pairs and the over-match guards. (review #3)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Thanks for the review! Took all three suggestions in daecf59:

  1. Tightened the filename match to repo-relative path fragments. Confirmed Rolldown prints the full path (../../core/mcp/remote/node/server.ts, etc.), so BENIGN_PAIRS now uses core/mcp/remote/node/server.ts / core/storage/store-io.ts / core/auth/node/secret-store.ts instead of basenames. No chance of dropping an unrelated server.ts warning now.

  2. Added the drift note. The file header now says the matcher tracks Rolldown's current message format and that the failure mode is safe (a format change makes the filter a no-op → warnings come back, nothing else is affected).

  3. Added a unit test. Exported isBenignWarning/BENIGN_PAIRS, guarded the stream patching behind !process.env.VITEST (so importing the module under Vitest doesn't monkey-patch the runner's streams), and added a .d.mts so the pure helper is importable from TypeScript. The test (src/test/server/quiet-config-warnings.test.ts) locks the signature pairs and covers the over-match guards: a real-dependency UNRESOLVED_IMPORT and a same-dep-different-file warning both pass through, and it asserts the pairs use path fragments.

Re-verified npm run dev is still clean with the tightened matcher (0 warnings, 0 code-frame remnants), and npm run validate is green.

@cliffhall
cliffhall merged commit 8be1680 into v2/main Jun 1, 2026
1 check passed
@cliffhall
cliffhall deleted the v2/quiet-dev-config-warnings branch June 1, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant