feat(cli): preview --detach — a background server that manages its own lifecycle#2132
Open
WaterrrForever wants to merge 3 commits into
Open
feat(cli): preview --detach — a background server that manages its own lifecycle#2132WaterrrForever wants to merge 3 commits into
WaterrrForever wants to merge 3 commits into
Conversation
…n lifecycle Agent sessions start preview as a background task, tying the server's lifetime to the task runner in both directions: reaped mid-session, the URL already handed to the user goes dead; never reaped, stale servers pile up serving old bundles. --detach starts the embedded studio as a detached child, waits for it to report ready through .hyperframes/preview/server.json, prints the URL + project link, opens the browser, and returns — the command exiting no longer takes the server with it. The detached server watches its own lifecycle instead of a terminal: an idle-hour timeout (HYPERFRAMES_PREVIEW_IDLE_TIMEOUT_MS, 0 disables) plus an optional explicit --owner-pid — no process-tree guessing for a default owner. --stop stops this project's server (per-project counterpart to --kill-all). Plain foreground preview is unchanged. Shape borrowed from Every's compound-engineering visual-probe helper (detached start + state-file handshake + self-watching lifecycle).
CodeQL (js/file-system-race): the log file could change between existsSync and readFileSync. Read inside try/catch instead — a missing log just means the child died before writing anything.
CodeQL (js/file-system-race) still paired the by-path read with the earlier append-open. Open the log once for reading and read through the descriptor — the open is the only path operation, and a missing log still just means the child died before writing anything.
| let tail = ""; | ||
| try { | ||
| // Open once and read through the descriptor — no path re-check to race. | ||
| const tailFd = openSync(paths.log, "r"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
preview --detachstarts the embedded studio as a detached child, waits for it to report ready, prints the URL plus a ready-to-hand project link, opens the browser, and returns — the command exiting no longer takes the server with it. The server then watches its own lifecycle instead of a terminal: an idle timeout, plus an optional owner pid.preview --stopstops this project's server (per-project counterpart to the existing--kill-all).Changes
server/detachedPreview.ts(new) — the parent↔child handshake and the lifecycle policy:.hyperframes/preview/server.jsonkeyed by a per-launch nonce, log atserver.log— a stale file from a previous launch is never mistaken for this one.waitForServerStatere-reads once after noticing the child exited, so the reuse path (write state, exit immediately) isn't misread as a startup failure.detachedShutdownReason(pure) — exit when the explicit--owner-pidis gone, or after an idle hour (HYPERFRAMES_PREVIEW_IDLE_TIMEOUT_MSto tune,0disables). Deliberately no process-tree guessing for a default owner: the pid chain through npx varies by harness, and a wrong guess kills the server under the user.commands/preview.ts:--detach(parent): spawn a detached child (--detached-child, internal), wait on the state file, print the summary +Openlink (--jsonfor the machine shape), open the browser, exit. On failure, print the log tail instead of a bare error.already-runningreuse (then exits; the existing server's lifecycle stays with whoever started it). Request activity feeds the idle clock; a 30 s watcher SIGTERMs into the existing graceful-shutdown path, which also removes the state file.--stop: SIGTERM the server(s) whose projectDir matches this project.hyperframes-clipreview-render.md: the new flags + "From an agent, prefer--detach" guidance; skills-manifest regenerated.Guardrails
--detachis passed — plainpreviewkeeps today's foreground behavior exactly; under the Vite dev/local-studio modes--detachwarns and runs foreground.clearServerStateonly deletes its own launch's file — an older child shutting down can never clobber a newer server's state.Tests / verification
--detach --jsonreturns once serving with the full state payload; the API answers;--stopSIGTERMs it and graceful shutdown removes the state file.