Skip to content

fix(cli): keep the shared ESM cache when another dev server holds the port - #3582

Merged
kojiwakayama merged 1 commit into
mainfrom
fix/dx-20260811-b2-28
Aug 11, 2026
Merged

fix(cli): keep the shared ESM cache when another dev server holds the port#3582
kojiwakayama merged 1 commit into
mainfrom
fix/dx-20260811-b2-28

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Found during a DX dogfood walk of the developer journey.

Symptom

Running veryfront dev a second time in the same project, while the first
server is still up, destroys the shared .cache.

Measured on the parent commit with a seeded cache directory:

--- cache BEFORE second dev ---   6
--- cache AFTER second dev ---    0

The second command took the still-running server's compiled modules with it.
The surviving server then has to recompile everything on the next request; the
original walk saw 432 .mjs files go to 0.

Root cause

cli/commands/dev/handler.ts cleared the caches unconditionally, before it even
called devCommand:

// Clear stale ESM caches to prevent module resolution issues from previous runs
await clearAllLocalCaches();

const { devCommand } = await import("./index.ts");

The dev port is not resolved until devCommand merges config.dev.port, and
not bound until startDevServer runs — several steps later. The cache directory
(getCacheBaseDir(), i.e. <project>/.cache) is shared by every dev server
rooted at the same project, so the destructive step ran for a process that had
no claim on the port.

Fix

Move the clear into devCommand, behind a probe on the resolved port:

if (clearLocalCaches) await clearLocalCachesIfPortFree(finalPort);

A taken dev port is the signal that another dev server is already serving this
project and still needs the modules it compiled, so the clear is skipped: the
second veryfront dev falls forward to a free port (#3562) and starts on a
cache it did not just destroy. A free port still clears, so the stale-module
behaviour the clear exists for is unchanged.

clearLocalCachesIfPortFree takes its clear and its probe as parameters — the
same seam startDevServerOnFreePort uses — so the decision is testable without
booting a dev server. The probe itself is isPortAvailable from
cli/commands/dev/port-fallback.ts, already used by the port fall-forward and
already cross-runtime.

One other consequence, intended: clearLocalCaches is opt-in, so veryfront demo's programmatic devCommand call keeps its current behaviour of never
clearing.

Regression tests

cli/commands/dev/dev-cache-guard.integration.test.ts, all three confirmed red
first against an unconditional clear:

  • a real held socket makes the probe report taken, and the seeded MDX-ESM and
    HTTP bundle entries survive;
  • a released port still clears them, so the guard did not simply disable the
    clear;
  • handleDevCommand driven against an unbindable port aborts inside
    devCommand with the seeded entries intact. That one locks the handler
    ordering that caused the bug — anything clearing ahead of the port probe has
    already destroyed the running server's modules by the time the command gives
    up.

Verification

  • deno test cli/commands/dev cli/commands/demo — 14 passed, 0 failed.
  • deno check cli/main.ts, deno lint, deno fmt clean.

Rebase note

Rebased onto current main, which now carries #3562 (veryfront dev falls
forward to a free port instead of hard-failing). The first cut of this PR added
its own cli/commands/dev/port-guard.ts probe and made a busy port a hard
failure. Both are gone: the hard failure would have reverted #3562, and
port-fallback.ts already ships the same bind-and-release probe. What survives
is the part #3562 does not address — the shared cache must not be wiped by a
process that does not own the port. Without this change the fall-forward makes
the bug quieter, not smaller: the second veryfront dev now succeeds on the
next port and still clears the first server's cache.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@kojiwakayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 17 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 94c15f7d-bfb5-46e4-b270-052a6717788a

📥 Commits

Reviewing files that changed from the base of the PR and between 718355c and 6426cc9.

📒 Files selected for processing (3)
  • cli/commands/dev/command.ts
  • cli/commands/dev/dev-cache-guard.integration.test.ts
  • cli/commands/dev/handler.ts

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 571c930f6b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/commands/dev/port-guard.ts Outdated
… port

Found during a DX dogfood walk of the developer journey. Running
`veryfront dev` a second time in the same project, while the first server
is still up, destroyed the shared `.cache` — the original walk saw 432
compiled `.mjs` files go to 0, forcing the running server to recompile
everything on its next request.

`cli/commands/dev/handler.ts` cleared the caches unconditionally, before
it even called `devCommand`. The cache directory (`getCacheBaseDir()`,
i.e. `<project>/.cache`) is shared by every dev server rooted at the same
project, so the destructive step ran for a process that had no claim on
the dev port.

Move the clear into `devCommand`, behind a probe on the resolved port.
A taken port is the signal that another dev server is already serving
this project and still needs the modules it compiled, so the clear is
skipped: the second `veryfront dev` falls forward to a free port (#3562)
and starts on a cache it did not just destroy. A free port still clears,
so the stale-module behaviour the clear exists for is unchanged.

`clearLocalCaches` is opt-in, so `veryfront demo`'s programmatic
`devCommand` call keeps its current behaviour of never clearing.

`clearLocalCachesIfPortFree` takes its clear and probe as parameters, the
same seam `startDevServerOnFreePort` uses, so the decision is testable
without booting a dev server.

Regression tests in cli/commands/dev/dev-cache-guard.integration.test.ts,
all three confirmed red first against an unconditional clear:

- a real held socket makes the probe report taken, and the seeded MDX-ESM
  and HTTP bundle entries survive;
- a released port still clears them;
- `handleDevCommand` driven against an unbindable port aborts inside
  devCommand with the seeded entries intact, which locks the handler
  ordering that caused the bug.
@kojiwakayama kojiwakayama changed the title fix(cli): take the dev port before clearing the shared ESM cache fix(cli): keep the shared ESM cache when another dev server holds the port Aug 11, 2026
@kojiwakayama
kojiwakayama force-pushed the fix/dx-20260811-b2-28 branch from 1e3741b to 6426cc9 Compare August 11, 2026 10:27
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

CI note: known flake, not from this change

The first run on the rebased commit went red on three checks, all from one test:

  • coverage shard 2/8HTTP Bundle Cache ... returns a signal-less cache follower after its bounded wait (src/transforms/esm/http-cache.test.ts), 578 passed | 1 failed;
  • tests (unit) and coverage gate — both are dependents of coverage-shards, so they fail in 2s on the shard result rather than on anything of their own.

That test is a known flake on main since #3553. This diff touches only cli/commands/dev/. Shard 2 run in full locally on this commit gives 579 passed | 0 failed. Re-running the failed jobs rather than touching the test.

tests (npm install smoke), tests (integration), ci (lint), ci (format) and ci (typecheck) all passed on that same run — including the npm path that the earlier Deno.listen probe broke.

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 1dd776a Aug 11, 2026
57 of 60 checks passed
@kojiwakayama
kojiwakayama deleted the fix/dx-20260811-b2-28 branch August 11, 2026 13:17
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