Lifecycle

The statuses an engagement moves through, and which of them are terminal.

An engagement has one status at a time, drawn from seven values: pending, queued, running, completed, failed, cancelled, blocked. This page lists the transitions that occur in the product, which is a smaller set than the seven names suggest.

The transitions

From To What causes it
— pending the engagement is created, by the wizard, the API, or a schedule firing
pending, blocked queued you submit it and every gated identifier clears the launch gate
pending, blocked blocked you submit it and some gated identifier does not clear the launch gate; nothing is queued
queued running the scheduler takes the job and starts the container
running completed the container exits 0 and returns a results payload
running failed the container exits non-zero, returns no payload, or the scheduler itself errors

Submitting is the only operator action that changes a status, and it is accepted only from pending and blocked. A request to submit an engagement in any other status is refused with a 400 naming the current one. The scheduled path applies the same two transitions to the engagement it creates from the schedule template.

Terminal and not

completed and failed are terminal. Nothing in the product moves an engagement out of either, and submit refuses both. A run you want repeated is a new engagement.

pending is where a created engagement waits. Creating an engagement and starting it are two separate actions: Create engagement saves it at pending and Start pentest on its detail page submits it. An engagement left at pending stays there indefinitely and consumes nothing.

blocked is not terminal. It means the launch gate refused the run because an identifier's ownership proof did not hold, and the response names each identifier and the reason. Clearing the gate is not the same check for every asset: a verified covering asset has its proof re-run over the network at each launch, while an attested one is a human claim with nothing to re-run, so only its expiry date is checked. Publish the missing record, or renew the attestation, and submit the same engagement again with POST /api/engagements/{id}/submit, or with an API key through POST /api/v1/engagements/{id}/submit (Engagements). The console shows Start pentest only on a pending engagement, so it has no button for this. See Ownership verification.

queued and running are the in-flight statuses, and they are the two the plan cap counts. Neither can be interrupted: there is no cancel, pause or abort in the console or the API.

cancelled is in the status vocabulary and the console knows how to display it, but no code path assigns it. No engagement reaches that status.

What each status allows

  • Report generation requires completed. A request for any other status is refused with a 400.
  • Locker export requires a seal, which is written only when the container exits zero with a results payload — a weaker condition than finishing the work. An unsealed engagement is refused with a 409. See Sealing.
  • The live progress stream is opened by the console only while the status is queued or running; once a run ends the console stops showing it. The trace is retained for 24 hours and stays fetchable from GET /api/engagements/{id}/progress — see Running an engagement.
  • Findings are written when the run is marked completed, in the same step. A failed engagement has no findings rows.

Where the status is written

pending is set at creation. queued and blocked are set by the submit endpoint, or by the campaign ticker on the scheduled path. running, completed and failed are set by the scheduler as it processes the job — running before the container starts, and one of the other two once it has exited.

A status write that fails is logged and swallowed rather than retried, so a database fault during a run leaves the engagement on its previous status while the run itself continues. An engagement stuck at queued or running long after its trace ended is the shape that problem takes.

Last updated 2026-09-24