List a repository's workflow runs (newest first)
GET/api/repos/{owner}/{repo}/workflow-runs
listRepositoryWorkflowRuns
Keyset-paginated, newest-first. Pass `next_cursor` from a previous page as `cursor` to fetch older runs. All filters are optional and combine conjunctively.
Authentication optional — this operation also accepts anonymous requests. When authenticating, use a bearer token or a session cookie.
curl
curl -X GET 'https://neosource.dev/api/repos/OWNER/REPO/workflow-runs' \
-H 'Authorization: Bearer $NEOSOURCE_TOKEN'fetch
fetch("https://neosource.dev/api/repos/OWNER/REPO/workflow-runs", {
method: "GET",
headers: {
Authorization: "Bearer $NEOSOURCE_TOKEN",
},
});neo
neo actions list OWNER/REPOPath parameters
ownerrequiredRepository owner or organization slug.
reporequiredRepository name.
Query parameters
workflow_idOnly runs of this workflow (UUID).
statusOnly runs with this status (`queued`, `running`, …).
run_numberOnly the run with this per-workflow ordinal.
trigger_commitOnly runs whose triggering event pointed at this exact commit (40-char git hex). How `neo push --runner` finds its push's runs.
cursorOpaque pagination cursor from a previous response's `next_cursor`.
limitPage size; default 30, max 100.
Responses
200Runs page
application/json
Standard errors
Bodies documented once for the whole API — see standard errors.
- 400Bad Request — one of: invalid_input
- 403Forbidden — one of: forbidden
- 404Not Found — one of: not_found
- 423Locked — one of: busy
- 429Rate limited — retry after the `Retry-After` header
- 500Internal server error
- 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
- 504Gateway timeout — the request exceeded the server's handling budget
Schemas
Referenced above. Listed here rather than expanded inline, so the same definition is not repeated at every level.
WorkflowRunEvent
A [`WorkflowRunCause`] with its payload stripped — the closed event vocabulary run list rows surface (`WorkflowRunResponse.event`). The serde `snake_case` strings are the wire vocabulary (pinned by the `enum_wire_consistency` tests).
"push""change""manual""schedule""pull_request""merge_group"
WorkflowRunStatus
Lifecycle of a workflow run. Mirrors the GHA-shaped surface that the SPA renders. The serde `snake_case` strings are the wire vocabulary the API emits (pinned by the `enum_wire_consistency` tests).
"pending_approval""queued""quota_blocked""concurrency_blocked""running""cancelling""succeeded""failed""cancelled"
WorkflowRunTestSummary
Per-run test results parsed from a `JUnit` report artifact at run completion. Counts are individual testcases.
failedrequiredfailuresrequiredFirst N failing tests (server caps the stored list).
itemspassedrequiredskippedrequiredtotalrequired
ToolchainManifest
Everything one run resolved, keyed by [`ToolchainKey::storage_key`]. Written once per run and read by every later reader — the second matrix leg, a re-run, a replay. Authoritative over the constraint that produced it: a re-run re-materializes these artifact identities rather than asking the catalog again (ADR 0083 §2).
catalog_versionsha256 of the catalog file that produced these entries. **Provenance only, never a lookup key.** The catalog is compiled into the binary, so a server running catalog C′ cannot evaluate against pinned catalog C — the rows are not in the process. Pinning a hash you cannot look up is a promise the deployment model cannot keep, which is why authority sits on the OUTPUT (ADR 0083 §2).
entries`storage_key` → what it resolved to.
additional propertiesunreadable`storage_key` → why a tolerant reader could not interpret that entry. **Populated only by a reader, never by the server.** The server writes what it can serialize, by construction, so this is empty in every row of `workflow_runs.toolchain_manifest`. A reader fills it in when it meets a [`ToolArtifact`] kind it predates and sets that one entry aside rather than failing the whole payload. A missing key and an unreadable key are NOT the same state: missing takes the late-bound resolve route and, failing that, an approximating local table; unreadable must REFUSE at the point of use. A job that never asks for an unreadable key runs untouched — that is what makes setting one entry aside safe at all. Additive on the wire (`default`, and skipped when empty), so an older reader ignores it and the common empty case costs nothing.
additional properties
DistLayout
How a dist archive unpacks into the tool-cache root.
"strip_one""flat"
StorePathArtifact
The `store_path` artifact's payload, with its invariants enforced. Two of them, and neither was a type before: 1. **`store_path` is a well-formed store path UNDER THE REAL STORE.** It deserializes straight out of `workflow_runs.toolchain_manifest` jsonb and [`ToolchainManifest::has_store_path`] is the agent's whole authorization surface, so as a raw `String` this answered "recorded" for anything that byte-matched a recorded entry — and left the grammar as the daemon's job alone rather than a property of the value. It takes BOTH halves to be that claim, and only the first was a type before: [`NixStorePath`] carries the GRAMMAR and is root-agnostic on purpose (the daemon's root is env-configurable and its own tests point it at a directory of their own), so `/etc/<hash>-passwd` is a shaped path and that type accepts it by design. [`StorePathArtifact::new`] pins the ROOT to [`NixStoreRoot::default`], on construction and on deserialize alike, which is what makes a recorded artifact naming anything outside the real store unconstructable. 2. **`bin_output` is one of `outputs`.** The `deno` → `denort` lesson is that which output carries `bin/` is index-ANSWERED, never guessed, and an answer naming an output the derivation did not have is not an answer. That was prose in a doc comment; it is checked here. `revision` stays an unvalidated `String` on purpose, and the asymmetry is the point: it is provenance only. No decision is made on it — that is exactly why [`ToolArtifact::nixpkgs_ref`] does not return it — so a malformed revision misleads a reader without routing anything, while a malformed store path is an authorization answer.
bin_outputrequiredWhich output `store_path` is. Index-answered, never guessed — the `deno` → `denort` lesson says a name heuristic is not enough. A member of `outputs` by construction.
outputsrequiredEvery output the derivation had, for provenance only.
itemsrevisionrequiredThe revision the index chose (ADR 0101 §2a records it "beside the store path"). Deliberately NOT named `nixpkgs_ref` and deliberately NOT returned by [`ToolArtifact::nixpkgs_ref`]: that accessor drives `group_by_ref` and the daemon's attr path on 8080, and a revision leaking there routes a ref that is not in `ALLOWED_REFS` — the 2026-07-31 403 storm.
store_pathrequired