Skip to content

fix(cloudflare): kill cloudflared properly on dev exit#1416

Open
Mkassabov wants to merge 1 commit into
alchemy-run:mainfrom
Mkassabov:fix/dev-tunnel-orphan-cloudflared
Open

fix(cloudflare): kill cloudflared properly on dev exit#1416
Mkassabov wants to merge 1 commit into
alchemy-run:mainfrom
Mkassabov:fix/dev-tunnel-orphan-cloudflared

Conversation

@Mkassabov

Copy link
Copy Markdown
Collaborator

Summary

Fixes a 530 error when restarting alchemy dev with dev: { tunnel: true } after switching Cloudflare profiles (or just restarting the dev server) on Windows.

Root cause

idempotentSpawn runs the child with shell: true, so the PID it records in .alchemy/pids/tunnel.pid.json is the cmd.exe wrapper, not cloudflared.exe. On cleanup we process.kill the wrapper, but Windows doesn't auto-kill its children — so cloudflared.exe orphans.

On the next alchemy dev run, one of two things happens:

  1. The orphan is still alive and races against a freshly-spawned cloudflared. Whichever loses the race produces a stale trycloudflare.com host that gets baked into the deployed tunnel-proxy worker's TUNNEL_HOST binding. Requests forward to a dead tunnel → 530.
  2. The state-file PID happens to still resolve (process.kill(pid, 0) is lenient) and idempotentSpawn reuses it, skipping the new spawn entirely — so the deployed TUNNEL_HOST points at a long-dead trycloudflare URL.

It worked on the first profile and broke on the second because no orphan exists before the very first dev run. It worked with tunnel: false because the cloudflared codepath is bypassed entirely.

Reproduced with two profiles + the playground worker; Get-Process cloudflared showed PID 54172 while tunnel.pid.json stored PID 33132 (the dead cmd.exe), confirming the orphan.

Fix

  1. Add a shell?: boolean option to idempotentSpawn, default true (preserves current behavior for user-provided dev commands that rely on .cmd resolution / shell features). When false, parse cmd into argv and spawn directly — the stored PID is then the program's own PID.
  2. Pass shell: false from the two cloudflared spawn sites (cloudflare/miniflare/tunnel.ts and cloudflare/quick-tunnel.ts); both run cloudflared tunnel --url <url>, which needs no shell features.
  3. As defense-in-depth for the remaining shell: true callers (website / bun-spa dev commands), use taskkill /T /F /PID in kill() on Windows so the entire process tree is torn down instead of just the shell.

Test plan

  • Repro: switch profile + restart dev → 530 (before fix)
  • With patch installed in playground: switch profile + restart dev → worker responds normally
  • Get-Process cloudflared after Ctrl+C now shows no orphan
  • POSIX still works for the shell: false callers (cloudflared is on PATH, no shell features used)
  • Website/bun-spa dev commands (npm run dev, etc.) still work — they remain shell: true

When `alchemy dev` is restarted (e.g. switching Cloudflare profiles),
the previous run's `cloudflared` process would orphan on Windows. The
stored PID in `.alchemy/pids/tunnel.pid.json` was the `cmd.exe` shell
wrapper, so cleanup killed the wrapper but left `cloudflared.exe`
running. The next dev session then either reused a stale trycloudflare
URL or raced against the orphan, baking a dead `TUNNEL_HOST` into the
deployed tunnel-proxy worker and producing a 530 on the new profile.

- Add a `shell` option to `idempotentSpawn` (default `true` to preserve
  current behavior for user-provided dev commands that rely on `.cmd`
  resolution and shell features). When `false`, parse `cmd` into argv
  and spawn the program directly so the stored PID is the program's.
- Pass `shell: false` from the cloudflared spawn sites
  (miniflare tunnel + quick-tunnel) since those are direct executables.
- On Windows, fall back to `taskkill /T /F /PID` in `kill()` so that
  the existing `shell: true` callers (website / bun-spa dev commands)
  also tear down their child trees instead of orphaning them.
@pkg-pr-new

pkg-pr-new Bot commented May 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/alchemy@1416

commit: 1e30679

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