fix: audit fallout — tunnel port, orphaned dev processes, scrollback clear
Auditing for repeats of the `pnpm dev --tunnel` bugs turned up the same mistakes elsewhere. Remote tunnels assumed 4040. ProjectEngine's Cloudflare quick tunnel hardcoded http://localhost:4040, so a dashboard on an explicit --port, a PORT override, or runDashboard's EADDRINUSE rebind published a PUBLIC tunnel to whatever else held 4040. The dashboard now records its bound port (setLocalDashboardPort, from both runDashboard and headless serve) and the tunnel reads it, keeping 4040 only as the pre-report default. register-discovery-routes already derived its port from req.socket.localPort and is untouched. Stopping the dev wrapper orphaned everything it started. It installed no signal handlers, so teardown only ran from the child's close handler: signalling the wrapper left the dev server AND its cloudflared alive — observed twice, four surviving processes each time, including a public trycloudflare URL still serving a dev server believed to be down. Ctrl-C hid it by signalling the whole process group. SessionTerminal appended scrollback instead of clearing first, though the server sends it as a separate frame precisely so the client can clear. Latent today because every reattach builds a fresh xterm; a duplicated-history bug the moment an in-place reconnect appears. And BackupManager's centralDbPath is gone: written, never read, and a leftover of the removed SQLite backup — the same class of stale artifact that onboarding was using as evidence about a Postgres install. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -315,6 +315,37 @@ async function warnIfDistStale() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:DevWorkflow 2026-08-19-04:00:
|
||||
Stop the dev server AND the tunnel when this supervisor is signalled. Teardown used to live only in
|
||||
the child's `close` handler, so `kill <wrapper-pid>` (or any supervisor-style stop) killed the
|
||||
wrapper and left the dev server and its cloudflared running as orphans — observed twice, four
|
||||
processes surviving each time. Interactive Ctrl-C hid it because the terminal signals the whole
|
||||
process group; anything that signals only this process did not.
|
||||
|
||||
An orphaned tunnel is the dangerous half: a public trycloudflare URL keeps serving the dev server
|
||||
after the operator believes it is down. Forward the signal, give the child a moment to exit on its
|
||||
own, then leave.
|
||||
*/
|
||||
let shuttingDown = false;
|
||||
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
||||
process.on(signal, () => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
devTunnel?.stop?.();
|
||||
if (appChild && !appChild.killed) {
|
||||
appChild.kill(signal === "SIGHUP" ? "SIGTERM" : signal);
|
||||
// The child owns a graceful shutdown path (draining agents, stopping Postgres); give it room,
|
||||
// then stop waiting so a wedged child cannot pin the terminal open.
|
||||
const forceExit = setTimeout(() => process.exit(0), 10_000);
|
||||
forceExit.unref?.();
|
||||
appChild.once("close", () => process.exit(0));
|
||||
return;
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
await warnIfDistStale();
|
||||
|
||||
if (!prebuildCommand) {
|
||||
|
||||
Reference in New Issue
Block a user