Skip to main content
execute runs a deployed workflow definition. It resolves a session, creates one execution against it, prints the debugger URL, and — with --watch — tails the execution’s events until it reaches a terminal status. It is the command that closes the loop after deploy: the same manifest that says what to deploy also says what to run, so a CI job can deploy a revision and exercise it without knowing an id.

Synopsis

--definition behaves differently here than anywhere else in the group: it is required and takes one reference, not a repeatable filter. One invocation is one execution.

What it does

  1. Resolves the blob and revision. This is a read — a commit-phase revision executes fine.
  2. For a bare alias (no /), lists the revision’s definitions and matches the manifest, refusing an alias the manifest does not carry with DEFINITION_NOT_ACCESSIBLE.
  3. For a bare alias, refuses one the manifest declares only as a playground entry with DEFINITION_NOT_EXECUTABLE, and a workflow entry not deployed on the revision with DEFINITION_NOT_ACCESSIBLE — both before creating a session or an execution.
  4. A qualified reference (containing /) skips steps 2–3 entirely — it is parsed client-side into a workflow object, and create_execution resolves and access-checks it server-side in step 6. A qualified reference the platform refuses is therefore refused only after step 5, so a session that step created stays behind.
  5. Resolves --session to an id, creating the session if the reference is a new alias.
  6. Creates the execution, sending target: {"session": "<id>"} and workflow as --definition was typed — {"definition": "<alias>"} for a bare alias, the parsed object for a qualified reference — and builds its debugger URL.
  7. With --watch, polls the execution’s events until a terminal status; with --open, launches a browser.
Under --json the payload carries execution_id, session_id, session_created, definition, status and url, alongside the envelope’s schema_version. The payload is emitted even when the run fails — the result is printed before EXECUTION_FAILED is raised, so a --json consumer still gets the execution id it needs to go look at what happened. --open launches a browser only when --json is off. The URL is in the payload either way, so a wrapper can open it itself; a subprocess that opens a browser behind a machine consumer’s back is not a side effect the CLI takes on its own.

How --session is resolved

Every workflow execution belongs to a session. Omit --session and one is created for this run. When you do pass a reference, the CLI still resolves it client-side, even though target.session takes an id or an alias, resolved in the route’s revision, just as get_session does. execute resolves it anyway for two reasons: a reference that is a new alias needs its session created before an execution can run against it, and a session that is not open needs to be refused before anything is created rather than surfacing later as an opaque conflict out of create_execution. So execute calls get_session with whatever you passed, and sends only the resolved id onward, as target: {"session": "<id>"}. The alias itself is never sent. What happens when the reference does not resolve depends on its shape, because the platform reports absent and not yours as the same 403: A session that resolves but is not open — closed, closing, or deleting — is refused with SESSION_NOT_OPEN before the execution is attempted. The CLI will not reopen it for you. Reporting it here, where you can still act, is the point: the alternative is an opaque conflict out of create_execution after the fact.

The definition is sent as typed

execute sends --definition to create_execution exactly as you typed it: a bare alias as {"definition": "<alias>"}, a qualified reference as the object its path form parses into. Alias uniqueness is scoped per (category, alias), so one alias may legally name both a workflow and a playground definition on the same revision — and the platform looks an alias up in the workflow category only, so the playground one is never picked instead. A shared alias resolves the same way in the manifest. --definition takes a bare alias with no category qualifier, so on a manifest that declares one alias in both categories, execute checks the workflow entry, whichever of the two the manifest declares first. Only an alias declared solely as a playground entry stops at DEFINITION_NOT_EXECUTABLE. ls shows each entry with its category. A qualified reference skips the manifest, so none of this local matching applies to it: the platform resolves it, with the same category filter on an alias. A qualified reference naming a playground definition by id is refused by the platform as invalid_definition_category, which execute reports as DEFINITION_NOT_EXECUTABLE.

--watch polls

--watch follows the execution by polling its event listing every two seconds. It does not open a WebSocket. Each poll reads one page — up to ten events, the platform’s default — created after the last event it printed, since the platform’s created_since filter is strictly exclusive. When the status goes terminal it reads one more page, so the final lines of a fast workflow usually print. The printed tail is not the complete record. Event timestamps are not unique, and the events index is updated a moment after each write, so an event that shares the last printed event’s created_at, or is indexed after a later event was read, can be skipped. A run that ends with more unread events than the final page holds loses the rest. The execution’s full event log — the debugger, or List Execution Events — is the authority. Events print one per line as they arrive, as the event’s type and message joined by a colon. An event with no message prints its type alone:
Two outcomes other than success:
  • A terminal failed status raises EXECUTION_FAILED after the result is printed. The events above the error are the platform’s own account of what went wrong.
  • Fifteen minutes without a terminal status raises the advisory EXECUTION_WATCH_TIMEOUT and stops tailing. The execution is still running on the platform and is unaffected — only the tail gave up. The status in the payload is the last one actually observed, not an empty value.
Without --watch the command returns as soon as the execution is created, and the reported status is whatever the platform assigned at creation — typically creating. That is the right shape for fire-and-forget; it is not a claim that the run succeeded.

When the outcome is unknown

Creating the session and creating the execution are writes, and the CLI repeats a write only when the failure proves it never ran: an HTTP 429, which is API Gateway’s throttling, or a connection that was never made. A 5xx, a body that is not JSON, a read timeout or a dropped connection can arrive after the platform has done the work, so those surface at once instead of being retried:
  • Creating the execution raises EXECUTION_OUTCOME_UNKNOWN: a run may have started. The message names the session and links its page in the web debugger, which lists the session’s executions. Look there before running the command again — running it blind can start a second run.
  • Creating the session keeps its transport code, API_TRANSIENT_ERROR or API_NETWORK_ERROR, and says the session may exist but nothing has run in it, so running the command again is safe. With the same --session alias, the rerun finds that session and uses it.
A 429 or a failed connect still ends as API_RATE_LIMITED or API_NETWORK_ERROR once its retries run out: nothing ran, so running the command again is safe.

Errors

EXECUTION_FAILED is the one code here that means the platform did its job: the definition ran and the run failed. Everything above it means the run never started. EXECUTION_OUTCOME_UNKNOWN means it may have. Every code above, with its remediation, is in Error codes.

See also