/** * crew#1145: the daemon never ends without saying why. * * On 2026-10-26 a freshly upgraded daemon logged `WICKED_CREW_READY`, then exited about a second * later; its log ended with a blank line and `Node.js v26.11.1` — Node's fatal-exception trailer * with the error text itself missing. That fingerprint reproduces with a full disk: crew's logger * writes to stdout, the launcher points stdout at `daemon-stdout.log`, a write that gets ENOSPC (or * EPIPE, when stdout is a pipe whose reader went away) is emitted as an `'error'` on * `process.stdout`, and with no listener Node turns it into an uncaught exception, whose own report * goes to the same full file and is lost. The host was at 96% used and dipping at the time. * * So, for the daemon (`wicked-crew serve`) only: * * - stdout / stderr `'error'` is HANDLED: the failure is reported once per stream and code * (with the state-home volume's free space), and the daemon keeps serving. `GET /diagnostics` * and the Nerve Center still work; the console log is what is lost. * - `uncaughtException` is reported with its stack, then the process exits 1 (continuing after an * arbitrary throw is not safe). * - `unhandledRejection` is reported with its stack and the daemon keeps serving: a forgotten * `.catch` on a background promise is a defect to fix, not a reason to drop every run's * control plane. * - `exit` always says the code and the recorded reason; with none recorded it adds the stack of * the `exit` call, which names a `process.exit` caller (or shows the loop drained). * - `SIGHUP` (a closed controlling terminal, `kill -HUP`) is reported and the daemon keeps serving. * Node's default for it is to die with no `exit` event, so no reason could ever be written; a * daemon is stopped with SIGTERM. Under `nohup` (how the rig launches it) SIGHUP was already * ignored, so this changes nothing there. SIGKILL cannot be observed by any process. * * Every report is written SYNCHRONOUSLY to stderr (fd 2) and appended to `/daemon-exit.log` * (the registered `daemon-` prefix of the state-home fence), each write on its own try: when the * disk is full one of them may still land, and neither can throw. A stderr that is already known * to fail is not written again. */ /** The record every guard report is appended to, under the daemon's state home. */ export declare const EXIT_RECORD_FILENAME = "daemon-exit.log"; export interface ProcessGuardOptions { /** The daemon's state home (where the exit record lives, and whose volume's free space is reported); `null` = unknown. */ stateHome: () => string | null; /** The process to guard (tests hand a stand-in). */ proc?: NodeJS.Process; /** Write one line to stderr synchronously (tests capture it). Default: `writeSync(2, …)`. */ writeErr?: (line: string) => void; /** Append one line to the exit record (tests capture it). Default: `appendFileSync`. */ appendRecord?: (path: string, line: string) => void; /** Free / total bytes of the volume holding `path` (tests stub it). Default: `statfsSync`. */ freeSpace?: (path: string) => { free: number; total: number; }; /** End the process after a fatal report (tests stub it). Default: `proc.exit`. */ exit?: (code: number) => void; /** Clock for the record's timestamp (tests). */ now?: () => Date; } export interface ProcessGuards { /** Record why the process is about to end, for the `exit` line (a signal, a fatal boot error). */ noteExitReason(reason: string): void; /** Report one line now (stderr + the exit record). */ report(line: string): void; /** Remove every listener this installed (tests). */ uninstall(): void; } /** `err` as text with its stack when it has one — never throws, whatever was thrown. */ export declare function describeThrown(err: unknown): string; /** * Install the guards. Call once, early in `serve`. Answers the handle the shutdown path uses to * say why it is exiting. */ export declare function installProcessGuards(opts: ProcessGuardOptions): ProcessGuards; //# sourceMappingURL=process-guards.d.ts.map