> ## Documentation Index
> Fetch the complete documentation index at: https://extension.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Lifecycle stream for dev sessions

> The NDJSON lifecycle frames that Extension.js dev, start, and preview stream in machine mode: starting, compiled, recompiled, compile-failed, ready, browser-exited, and failed.

Follow a live session as one JSON frame per transition.

A terminating envelope cannot describe a session, so `dev`, `start`, and `preview` stream one schema-1 frame per lifecycle transition. Each frame is a complete [result envelope](/docs/contracts/result-envelope) on its own line.

## Turning the stream on

The stream keys off the `EXTENSION_OUTPUT` environment variable. Set it to `json` or `ndjson` and frames own stdout:

```bash theme={null}
EXTENSION_OUTPUT=ndjson extension dev ./my-extension --browser chromium --no-browser
```

While the stream is on, human copy that shares stdout moves to stderr. Error copy always stays on stderr, machine mode never hides a failure.

<Frame>
  <iframe className="w-full aspect-video rounded-xl" src="https://www.youtube-nocookie.com/embed/YvQWuq387ak?rel=0" title="Extension.js: the lifecycle stream" loading="lazy" allow="encrypted-media; picture-in-picture; fullscreen" allowFullScreen />
</Frame>

## Frame statuses

| Status | `ok` | When it arrives |
| - | - | - |
| `starting` | true | Once, when the session begins, after the config loads and the compiler exists. Carries `requestedPort` and the bound `port`. |
| `compiled` | true | The first successful compile. Carries `assets` and `durationMs`. |
| `recompiled` | true | Every later successful compile. |
| `compile-failed` | false | A compile finished with errors, first or later. |
| `ready` | true | Once, when `ready.json` is on disk for this session. |
| `browser-exited` | false | The browser died mid-session, see below. |
| `failed` | false | A session-level failure, for example the server never bound, or `ready.json` reports `error`. |

Every frame's `value` carries the session identity: `command`, `browser`, `distPath`, `pid`, `port`, plus `readyPath`, `eventsPath`, `runId`, `instanceId`, and `toolchainVersion` when known.

A run refused before the session begins sends one `failed` frame and no `starting` frame. A missing manifest, a config file that fails to load, and a remote URL with no usable archive all end that way.

## Compile failures

A `compile-failed` frame carries the compiler output inside `value.output`, so you never scrape stdout. The output is ANSI-stripped and capped at 2000 characters. When the cap cuts it, the frame sets `truncated: true`.

The first failure of a session uses code `E_FIRST_COMPILE`, every later one uses `E_COMPILE`.

The frame also carries `error.details`, one entry per diagnostic with its own code, file, and position, errors first, in the shape the [result envelope](/docs/contracts/result-envelope) describes under Compile diagnostics. The list is capped at 20 entries, and `truncated: true` is set when either the output or the list was cut.

```jsonl theme={null}
{"schema":1,"ok":true,"command":"dev","status":"starting","value":{"command":"dev","browser":"chromium","distPath":"/home/dev/my-extension/dist/chromium","pid":51234,"port":8080,"requestedPort":8080},"error":null,"warnings":[]}
{"schema":1,"ok":true,"command":"dev","status":"compiled","value":{"command":"dev","browser":"chromium","distPath":"/home/dev/my-extension/dist/chromium","pid":51234,"port":8080,"runId":"mdyq3k2p-a1b2c3d4","assets":12,"durationMs":841},"error":null,"warnings":[]}
{"schema":1,"ok":true,"command":"dev","status":"ready","value":{"command":"dev","browser":"chromium","distPath":"/home/dev/my-extension/dist/chromium","pid":51234,"port":8080,"runId":"mdyq3k2p-a1b2c3d4"},"error":null,"warnings":[]}
{"schema":1,"ok":false,"command":"dev","status":"compile-failed","value":{"command":"dev","browser":"chromium","distPath":"/home/dev/my-extension/dist/chromium","pid":51234,"port":8080,"runId":"mdyq3k2p-a1b2c3d4","output":"ERROR in ./content/scripts.ts\nModule parse failed: Unexpected token (12:3)","durationMs":204},"error":{"code":"E_COMPILE","message":"A recompilation failed after a change.","details":[{"code":"E_CONTENT_SCRIPT_SYNTAX","message":"Module parse failed: Unexpected token (12:3)","file":"content/scripts.ts","line":12,"column":3,"severity":"error"}]},"warnings":[]}
```

## Ready follows the contract

The `ready` frame reads `ready.json` before it fires. A compile can succeed while the browser refuses the extension, and the contract stays in `error` when that happens.

In that case the stream emits a `failed` frame with code `E_READY_ERROR_STATUS` instead, and `value.readyCode` names the contract's own error code.

The same frame arrives when the browser never started or exited before the extension loaded. `value.readyCode` is then `browser_launch_failed` or `browser_exited`. A Firefox whose debugger never answers counts as a launch failure.

A bad `--chromium-binary` or `--gecko-binary` pin also reports `readyCode: "browser_launch_failed"`, but its frame has `status: "usage"` and `error.code: "E_BROWSER_BINARY_INVALID"`, the code every command gives that pin.

A Chromium `dev` session whose CDP wire failed after the browser came up still sends `ready`, because the browser is up. That frame carries the fault on `warnings` as `E_CDP_...: message`, the same pair the contract stamps as `cdpFaultCode` and `cdpFaultMessage`, see [ready.json](/docs/contracts/ready-json). Reload and HMR cannot attach until the fault is fixed.

## Browser exits

A background watcher polls `ready.json` once per second for the launcher's exit stamp. When `browserExitedAt` appears, the stream emits one `browser-exited` frame.

The frame's code depends on the evidence in the contract:

* `E_PROFILE_LOCKED` when the contract shows a locked profile. The browser never started, another session holds the profile.
* `E_BROWSER_LAUNCH` for every other unexpected exit.

The frame's `value` carries `exitCode`, `exitSignal`, and `browserExitedAt` when the contract has them. A crash has no exit code, only a signal such as `SIGTRAP`.

## Next steps

* Read the contract behind the frames in [ready.json](/docs/contracts/ready-json).
* Drive the whole loop from an agent with [Driving the CLI](/docs/workflows/driving-the-cli).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.