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 alias, 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. Lists the revision’s definitions and matches the manifest, refusing an alias the manifest does not carry with DEFINITION_NOT_ACCESSIBLE.
  3. Refuses a playground entry with DEFINITION_NOT_EXECUTABLE, before creating a session or an execution.
  4. Resolves --session to an id, creating the session if the reference is a new alias.
  5. Creates the execution and builds its debugger URL.
  6. 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 resolves it client-side, and this is not an implementation detail — it is forced by an asymmetry in the platform. create_execution accepts a session id only, while get_session accepts an id or an alias. So execute calls get_session with whatever you passed and hands only the resolved id onward. 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 addressed by id, never by alias

The platform can look a definition up by alias, and execute deliberately does not use that path. The server’s alias lookup applies no category filter, while 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 an alias-addressed execution would resolve to whichever the query happened to return first. execute already holds the definition’s id from the listing it did in step 2, so this ambiguity never reaches the platform. The same shared alias is still worth knowing about locally. --definition takes a bare alias with no category qualifier, so on a manifest that declares one alias in both categories, execute takes the first matching entry in manifest order and does not report the collision. If that entry is the playground one, the run stops at DEFINITION_NOT_EXECUTABLE even though a workflow definition of that name exists. Use distinct aliases, or declare the workflow entry first. ls shows both, with their categories.

--watch polls, and that is exact

--watch follows the execution by polling its event listing every two seconds, remembering the last event’s created_at and asking for everything after it. It does not open a WebSocket. That is not a compromise. The platform’s created_since filter is strictly exclusive, so a poll that resumes from the last event it saw redelivers nothing and skips nothing — the tail is exact, not merely close enough. When the status goes terminal, the events are drained once more before returning, so the final lines of a fast workflow are not lost between the last page and the status read. 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.

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. Every code above, with its remediation, is in Error codes.

See also