Task Dependencies
Define execution order between tasks using dependency chains
Task Dependencies
Tasks can declare dependencies on other tasks, creating a directed acyclic graph (DAG) that controls execution order. A task with unresolved dependencies cannot be claimed by workers until every dependency satisfies the gate — which is not the same as every dependency reaching completed. A cancelled dependency satisfies the gate, and a completed one whose PR is still open does not. See Blocking Behavior for the exact rule.
How It Works
Each task has an optional dependsOn field — an array of task IDs that must each satisfy the dependency gate before the task becomes claimable.
# Create a task that depends on two others
curl -X POST https://buildd.dev/api/tasks \
-H "Authorization: Bearer bld_xxx" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "ws_xxx",
"title": "Deploy to staging",
"dependsOn": ["task-id-1", "task-id-2"]
}'Dashboard UI
Creating Dependencies
Dependencies can be set in three places:
- New Task page — expand "Advanced options" to reveal the dependency selector
- Edit Task modal — open any task and use the dependency dropdown
- Quick Create modal — dependency selector available when a workspace is selected
The dependency selector is a searchable dropdown that shows all non-completed tasks in the workspace. Selected dependencies appear as removable chips with status indicators.
Viewing Dependencies
On a task's detail page, the Dependencies section shows:
- Each dependency as a clickable link to the parent task
- A status badge (pending, in progress, completed, failed) next to each
- A green checkmark with "All dependencies resolved" when all are complete
Sidebar Lock Icon
Tasks with unresolved dependencies display a padlock icon in the sidebar task list. This provides a quick visual indicator of which tasks are blocked without opening each one.
Blocking Behavior
When a worker calls claim_task, the system filters out any task that still has an
unsatisfied dependency. A dependency is satisfied when both of these hold:
- Its status is
completedorcancelled. - It is not a
completedtask that still has an open, unmerged PR.
Written as the gate evaluates it:
satisfied = status IN ('completed', 'cancelled')
AND NOT (status = 'completed' AND the dep has an open, unmerged PR)Two consequences are easy to get wrong in opposite directions:
cancelledsatisfies the gate. Cancelling a dependency is a deliberate "this will never be delivered" signal, so its dependents proceed rather than waiting forever. The open-PR guard in rule 2 does not apply to cancelled dependencies.completedis not always enough. If the dependency's PR exists and has not been merged, the dependent stays blocked even though the upstream task reads as done. This stops a downstream task from building on work that has not landed yet. The guard releases once the PR is merged — or once the PR is recorded as closed, since abandoned work will never land and holding dependents behind it would block them permanently.
Every other status — including failed, pending, and in_progress — blocks.
Note that failed blocks: a failed dependency does not resolve itself, so cancel it
(or fix and complete it) to release its dependents.
This means:
- Blocked tasks remain in
pendingstatus but are invisible to the claim system - Once every dependency satisfies the gate, the task becomes available for the next claim
- There is no automatic notification — workers discover newly unblocked tasks on their next claim attempt
Bypassing the gate
The gate is skipped entirely when a task has no dependencies (dependsOn is unset or
an empty array), or when the task has been force-started — starting a task with
forceOverride sets a bypass flag on the task that makes the claim query ignore the
dependency gate for that task.
Circular Dependency Detection
The plan approval system (see Planning Mode) validates dependency graphs using depth-first search before creating child tasks. Circular dependencies are rejected with an error.
API Reference
Create Task
POST /api/tasks| Field | Type | Description |
|---|---|---|
dependsOn | string[] | Optional. Array of task IDs that must complete first |
Update Task
PATCH /api/tasks/{id}| Field | Type | Description |
|---|---|---|
dependsOn | string[] | Replace the dependency list |
MCP
buildd action=update_task cannot change dependencies. Its handler copies a
fixed set of fields — title, description, priority, project, status,
backend, maxLoops — into the PATCH body and ignores everything else. A
dependsOn argument is dropped without an error, so the call returns
Task updated: ... and the dependency list is unchanged.
buildd action=create_task does accept dependsOn, so a task can declare its
dependencies at creation time over MCP:
buildd action=create_task params={
title: "Deploy to staging",
description: "...",
dependsOn: ["task-1", "task-2"]
}To change dependencies on a task that already exists, use the REST endpoint above:
curl -X PATCH https://buildd.dev/api/tasks/{id} \
-H "Authorization: Bearer bld_xxx" \
-H "Content-Type: application/json" \
-d '{ "dependsOn": ["task-1", "task-2"] }'