Skip to main content
Poll one JSON file to know when a session is ready, broken, or gone. Every 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. The ready line on stdout is a stricter claim in a Chromium dev session: the launcher prints it only once the runtime inside the extension has connected (executorAttachedAt). When the browser is up and nothing connects within 15 s, the launcher prints what it is waiting on instead, stamps browserUpNotAttachedAt with a hint, and prints ready the moment the attach lands. 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.

Sessions that take act verbs hold ready

From the release after 4.1.39, a session that takes act verbs keeps status: "starting" until the extension’s background connects or 10 seconds pass. This applies to:
  • preview and start with --allow-control or --allow-eval. A build with no background does not wait, because nothing connects from it.
  • dev with --allow-control or --allow-eval and a browser that the session launches.
While dev waits, the contract carries awaitingExecutorSince. The field goes away when the background connects or the wait runs out. The wait is bounded, so ready still arrives for an extension that never connects, with no executorAttachedAt beside it. executorAttachedAt stays the field that says an act verb can run. On Firefox, a dev session that waits needs both the RDP port and the background before it reports ready.

Field reference (schema v2)

Fields that are always present: Fields that appear when known:

Dev additions

From the release after 4.1.39, a dev contract lists what the session has that a production build of the same source lacks. Each devAdditions entry carries a kind, a value, and a reason: The first three kinds are the difference between your manifest and the dev manifest. Each recompile replaces them. The launcher writes the browser_flag and browser_preference entries, and a recompile keeps them. A browser_flag entry says whether this launch passes the flag (applied) and what decided that (cause). cause is default, browser-flags, debugger-permission, or exclude-browser-flags. change names the one edit that flips the verdict. A browser_preference entry appears on Chromium when the profile outlives the session, as with --profile <path>. The session sets session.restore_on_startup to 5, so Chrome opens a new tab and does not restore the pages of the previous run. The value stays in that profile after the session ends. Its cause is default, preferences when your config sets the preference, or unavailable when the browser refused the write. See Browser preferences.
The identity card prints the same entries as Dev only and Dev flag lines. See Reading CLI output. Two flags follow the debugger permission. See Browser flags.

Error states

The code 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:
  1. The command field must match the waiting command.
  2. The pid must be alive. For dev, a dead producer always means stale, so polling continues.
  3. For start, a dead pid is accepted only when the contract is fresh. Freshness means ts, compiledAt, or startedAt is within the last 60 seconds.
A timeout exits with 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 holds events.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