---
title: "Cancel Build"
method: DELETE
path: "/api/v1/factory/builds/{build_id}"
tags: ["builds"]
---

# Cancel Build

`DELETE /api/v1/factory/builds/{build_id}`

## Request

- The document declares no server URL.
- Auth: one of:
  - HTTP bearer
  - HTTP basic

Cancel a build.

Handles both active builds (delegated to Build Coordinator) and
pre-dispatch builds (cancelled FS-only via an atomic row lock).

The operation is idempotent with respect to terminal state. When
the build has already reached a terminal state (``cancelled``,
``completed``, ``failed``, or ``timed_out``) — whether Build
Coordinator reports it or the local task row records it — the
response is still 200 and the ``status`` field carries that
terminal state. Coordinator statuses are normalized into the
effective vocabulary before they are surfaced: BC's ``timed_out``
surfaces as ``timed_out``, the same word a subsequent ``GET``
reconstructs from the footprint the callback path persists
(status='failed' + error_class='timeout'), so the two surfaces
always agree.

Cancelling a pre-dispatch build permanently retires its identity
tuple: ``uq_factory_build_identity`` dedup treats the cancelled
row like any other terminal build, so a later event expanding to
the same identity inserts nothing. Recovery is manual by design —
a cancel that ambient event traffic could overturn would not be a
cancel.

Outcomes:
    200 — Build cancelled, or already terminal. ``BuildResponse.status``
          reflects the effective state.
    404 — No build with the given ID.
    409 — The coordinator does not know the build yet because its
          dispatch is in flight (the worker commits its claim
          before the HTTP submit). Retry with backoff.
    502 — Build Coordinator unreachable or returned an unexpected
          error; or the coordinator has no record of a build whose
          dispatch window has long passed (lost to a restart or
          purge, or stranded by a worker that died between claim
          and submit).

An audit log line is emitted on every path, including unhandled
exceptions (``outcome=internal_error``).

## Path parameters

- `build_id` integer, required

## Response `200`

Build cancelled or already terminal; status field carries the outcome

- BuildResponse: Build-specific details with optional task sub-object. The task field is None for undispatched builds (task_id IS NULL in factory_builds). The status field is the build's effective current status — the EffectiveBuildStatus vocabulary — computed server-side from the freshest authoritative source: - Pre-dispatch (task_id IS NULL): computed from factory_builds.cancelled_at — ``"cancelled"`` when set, else ``"pending"``. Neither word is stored; the timestamp is the only persisted pre-dispatch state. - Dispatched: tasks.status (``"running"``, ``"completed"``, ``"failed"``, ``"cancelled"``), with ``"timed_out"`` reconstructed from the persisted footprint of an execution timeout (status='failed' + error_class='timeout'). A submit-time ``dispatch_timeout`` is a different failure — the build never observably started — and reads as ``"failed"``. - On cancel responses: Build Coordinator's returned status, which is fresher than the local row (which lags until BC's callback lands), put through the same derivation — BC's ``timed_out`` surfaces as ``"timed_out"``, agreeing with what a subsequent GET reconstructs once the callback persists the footprint. The field never carries a word outside the effective vocabulary. Staleness: after handoff the field tracks tasks.status, which only advances when Build Coordinator's terminal callback lands. When callbacks are disabled (no callback base URL configured — a supported worker configuration), the post-handoff status stays at the last persisted value (typically ``"running"``) indefinitely; a cancel response is then the only place a fresher coordinator-reported status appears.
  - `build_id` integer, required
  - `status` 'pending' | 'running' | 'completed' | 'failed' | 'timed_out' | 'cancelled', required: Effective status of a build, derived server-side and never stored. A dispatched build's task lifecycle is authoritative, with timed_out reconstructed from a failed task whose error class is 'timeout'; a build cancelled before dispatch is cancelled; an undispatched, uncancelled build is pending. These six values are exactly what the derivation can emit; emittable, filterable, and the whole vocabulary are the same set. The member order here is the documentation order.
  - `source_repo_url` string, required
  - `source_commit_sha` string, required
  - `nixpkgs_revision` string, required
  - `system` string, required
  - `attr_path` string, required
  - `build_type` string, required
  - `catalog_name` string, required
  - `exit_code` integer, nullable
  - `created_at` string, date-time, required
  - `task` TaskResponse: Generic task lifecycle — same shape for all operation types. The status field carries the task vocabulary — the persisted lifecycle words, distinct from the derived effective vocabulary on BuildResponse.status. The task sub-object reports the stored footprint the top-level status is derived from: a timed-out build's task still reads status='failed' + error_class='timeout' while the build-level status reads 'timed_out'.
    - `task_id` integer, required
    - `task_type` string, required
    - `status` 'running' | 'completed' | 'failed' | 'cancelled', required: Valid status values for tasks. Mirrors the CHECK constraint as of migration 1.8.0: status IN ('running', 'completed', 'failed', 'cancelled'). The claim CTE inserts tasks directly as 'running' (the claim is the dispatch; there is no separate queued phase). The three terminal values are written by ``process_callback`` and ``mark_task_failed``.
    - `error_message` string, nullable
    - `error_class` 'transient' | 'permanent' | 'timeout' | 'dispatch_timeout': Diagnostic class of a failed task. Mirrors the ``ck_task_error_class`` CHECK as of migration 1.8.2: error_class IN ('transient', 'permanent', 'timeout', 'dispatch_timeout'); the column is NULL for non-failure terminals. - TRANSIENT: says nothing of the build itself, such as a build the coordinator lost or gave no status for. - PERMANENT: will not improve on retry. - TIMEOUT: the coordinator reported an execution timeout — a build that ran and overran its limit. - DISPATCH_TIMEOUT: the submit HTTP call to the coordinator itself timed out, so the build's handoff never observably started. The last two are kept distinct so the read surface shows an execution timeout as timed_out while a submit timeout reads as failed.
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `started_at` string, date-time, nullable
    - `completed_at` string, date-time, nullable
  - `trigger` BuildTrigger: What scheduled a build.
    - `kind` string
    - `stabilities` string[]: The stabilities that named this build's nixpkgs revision when the build was scheduled. Empty when none did, as for a manifest build, which does not build against the revision. Not the revision's current stabilities, which can move after scheduling.
  - `published_package` PublishedPackage: The catalog package a build published.
    - `name` string, required: The published derivation's name, such as `hello-2.12.1`.
    - `version` string, nullable, required: The published derivation's version, null when it has none.
    - `source_rev_count` integer, required: The revision count of the source page the package was built from. One part of the build's coordinate: builds of the same source revision with different locked inputs or base page share it.
    - `description` string, nullable, required: The published derivation's description, null when it has none.
    - `license` string, nullable, required: The published derivation's license as the catalog records it, such as `MIT` or `[ MIT, Apache-2.0 ]`; null when it has none.

## Other responses

- `404`: Not Found
- `409`: Build's registration with the coordinator is still in flight; retry with backoff
- `422`: Unprocessable Entity
- `502`: Build Coordinator unreachable or returned an error, or has no record of the build

## Changes

- **2026-10-22** `a0a0401010b7`: 1 info
  - added the optional property `published_package` to the response with the `200` status
- **2026-10-01** `76701e311542`: 2 info
  - added the non-success response with the status `409`
  - added the optional property `trigger` to the response with the `200` status
- **2026-08-27** `f17e891a3eeb`: 6 breaking, 4 warning, 1 info
  - the `task` response's property type changed from `object` to no type for status `200`
  - removed the required property `task/created_at` from the response with the `200` status
  - removed the required property `task/status` from the response with the `200` status
  - removed the required property `task/task_id` from the response with the `200` status
  - …7 more
- **2026-07-28** `ffc271c8797c`: 14 warning
  - added the new `cancelled` enum value to the `status` response property for the response status `200`
  - added the new `cancelled` enum value to the `task/status` response property for the response status `200`
  - added the new `completed` enum value to the `status` response property for the response status `200`
  - added the new `completed` enum value to the `task/status` response property for the response status `200`
  - …10 more
- **2026-06-23** `7c0e206cb685`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/flox/apis/flox-factory-service/changes/api/v1/factory/builds/:build_id/delete.md)

---

[API](https://skmtc.dev/flox/apis/flox-factory-service.md) - [All operations](https://skmtc.dev/flox/apis/flox-factory-service/llms.txt) - [OpenAPI document](https://skmtc.dev/flox/apis/flox-factory-service/revisions/a0a0401010b7?raw)
