dev, start, preview, and build run writes dist/extension-js/<browser>/ready.json atomically on each compile. The current contract is schemaVersion: 2.
A preview with no build loads the source folder and still writes its contract to the project’s dist/extension-js/<browser>/ready.json, with every browser field a built preview carries.
Statuses
Two-phase readiness
status: "ready" means compiled, nothing more. The browser may still be launching, and the service worker may not be connected yet.
Act tooling (eval, storage, reload, open, inspect) needs the second phase. Wait until the contract carries runtime: "attached" and an executorAttachedAt timestamp before you drive the extension.
The attach stamp is idempotent and survives recompiles. An attach also clears any earlier extension_load_refused state, because the executor runs inside the guest.
Field reference (schema v2)
Fields that are always present:
Fields that appear when known:
Error states
Thecode field names the failure class. Four codes matter for automation:
A load refusal outlives the next successful compile. Only a new run (
starting) or a real executor attach clears it.
A dev session whose browser exited before the extension loaded stays in error across recompiles, because a recompile does not bring the browser back. A launch failure outlives recompiles too, until a browser does start or a new run begins.
Waiting with —wait
extension dev --wait and extension start --wait poll the contract every 250 ms and exit when it reports ready. Pair them with --output json for a machine result.
The wait loop refuses to trust stale files. Three checks run on every read:
- The
commandfield must match the waiting command. - The
pidmust be alive. Fordev, a dead producer always means stale, so polling continues. - For
start, a dead pid is accepted only when the contract is fresh. Freshness meansts,compiledAt, orstartedAtis within the last 60 seconds.
E_READY_TIMEOUT. A contract in error status fails the wait with its message, and the error.code of the result resolves the contract’s own code:
A
browser_launch_failed contract that carries browserLaunchFailedCode: "E_BROWSER_BINARY_INVALID" or "E_FLAG_NOT_SUPPORTED_HERE" fails the wait with that code and status: "usage" instead of E_BROWSER_LAUNCH.
Joining the session files
The same directory holdsevents.ndjson (compile timeline) and logs.ndjson (extension console output). Every row in both carries the runId from ready.json.
events.ndjson is truncated at every run start, so it only ever describes the current run. Event types are compile_start, compile_success, compile_error, browser_exited, browser_launch_failed, and shutdown. A browser_exited row mirrors the launcher’s exit stamp, with exitCode, exitSignal, and browserExitedAt, so the timeline says when and how the browser went. After it, every act verb answers E_CONTROL_UNAVAILABLE naming the exit and the restart, since no service worker reconnects until a new dev session relaunches the browser. A browser_launch_failed row mirrors the launch failure stamp, with browserLaunchFailedAt and reason.
Example
Next steps
- Read command results with the Result envelope.
- Stream the same session as frames with the Lifecycle stream.
- Wire the whole loop into tests with Playwright E2E.

