# Coming from signal-exit

> A signal-exit alternative with zero dependencies: closeout's onExit runs once on every exit path with a bounded deadline, and reports as --json or an agent event. No signal-exit drop-in yet — its compatibility row is planned, not graded.

Source: https://closeout.interlace.tools/docs/coming-from/signal-exit

**closeout** is a **signal-exit alternative**: exit handlers that run exactly once on every
path, terminal restore, and a bounded deadline so shutdown cannot hang. It is **not yet** a
signal-exit drop-in, and this page says why.

## Migrate from signal-exit: rename the callback's arguments

There is no `closeout/signal-exit` subpath. Both packages export `onExit`, and both return
the function that unregisters the handler, but the callback is handed a different shape —
signal-exit passes `(code, signal)`, closeout passes one record:

```diff
- import { onExit } from 'signal-exit';
- const off = onExit((code, signal) => {
+ import { onExit } from 'closeout';
+ const off = onExit(({ path, code, signal, error }) => {
    releaseTheLock();
  });
```

`path` is `'exit' | 'beforeExit' | 'signal' | 'uncaught' | 'rejection'`, and `error` is what
was thrown or rejected on the two paths that have one.

The two neighbours in this layer **do** have one-import paths, each graded by its
incumbent's own suite:

```diff
- import exitHook, { asyncExitHook, gracefulExit } from 'exit-hook';
- import restoreCursor from 'restore-cursor';
+ import exitHook, { asyncExitHook, gracefulExit } from 'closeout/exit-hook';
+ import restoreCursor from 'closeout/restore-cursor';
```

## Is closeout compatible with signal-exit?

**There is no graded signal-exit row.** On the [Compatibility](https://burgee.interlace.tools/docs/compatibility) page,
which `npm run compat:page` generates from the oracle's last run, signal-exit is listed as
**planned**: its suite is vendored and runs, but the control does not yet clear its own
reference, and a control that cannot must not publish a rate. Until it is graded, closeout
does not claim signal-exit compatibility. The rows that are graded:

| | closeout | control |
| :-- | --: | --: |
| `closeout/exit-hook` | 21 / 21 | 21 / 21 |
| `closeout/restore-cursor` | 6 / 6 | 6 / 6 |

## What you gain over signal-exit

- **Exactly once, on every door.** A normal exit, an emptied event loop, Ctrl-C, `SIGTERM`,
  `SIGHUP`, `SIGQUIT`, an uncaught throw and an unhandled rejection all reach the same
  handler, once, even when two fire at the same moment.
- **Shutdown cannot hang.** A bounded deadline — `DEFAULT_DEADLINE` is 2000 ms — ends a
  shutdown whose handlers do not return, and the result names the ones that had not.
- **Phases, not registration order.** Handlers run in `flush`, `release`, `restore` order, so
  the cursor comes back after the file is flushed, whichever registered first.
- **One record, three readers.** The record your handler receives is what
  `reportToJson()` and `reportToEvent()` project, so a `--json` line and an agent event
  cannot disagree with what the handler was told.
- **Terminal restore is built in.** `hideCursor(stream)` registers its own restore.

SIGKILL is not deliverable to a listener, by design, and closeout does not claim it.

## When to switch from signal-exit

- Ctrl-C leaves a hidden cursor, a half-written file or a held lock.
- A shutdown handler that never returns has hung your CLI or your CI job.

The API, the phases and the deadline are on [closeout](/docs).
