# Blockers and blocked tasks

Source: https://codeherder.com/docs/blockers/

What a blocker is, how a task gets one, which ones clear themselves, and how to file, view, and resolve one.

A **blocker** is a record on a task explaining why it’s stuck. It’s usually one of two things: a hand-off a person needs to make, or an automatic note that the task is waiting on another task. A few automatic merge-wait notes fit neither kind — see [Notes that don’t change the task’s status](https://codeherder.com/docs/blockers/#notes-that-dont-change-the-tasks-status). A person-facing blocker moves the task to the reserved `blocked` status; an automatic dependency note doesn’t — the task stays where it was, just held out of the staffing queue until the wait is over. Either kind can also carry a release time, so it clears itself once that time passes. A task can carry more than one blocker at once.

This page covers both kinds: how each one ends up on a task, where you’ll actually meet one, and how to file, inspect, and resolve one.

## What a blocker is

A blocker records who filed it (a person, an agent, or CodeHerder itself), when, an optional link to a related task, and a reason.

## How a task gets a blocker

These move the task’s status to `blocked` — it drops out of its current stage until every one of them is resolved:

- **Someone files a blocker.** A person or an agent records that the task is stuck on something external — optionally with a future release time, covered in [Blockers that release themselves](https://codeherder.com/docs/blockers/#blockers-that-release-themselves) below. See [Filing a blocker yourself](https://codeherder.com/docs/blockers/#filing-a-blocker-yourself) for how.
- **An agent asks a question it can’t answer alone.** The blocker reads “Waiting for human answer to agent question” until you answer the question — from the Dashboard, the task itself, or wherever else you happen to meet the agent. See [When an agent needs your input](https://codeherder.com/docs/agent-input/).
- **An agent refuses its own stage.** An agent can decide mid-session that its work isn’t ready to go forward and end its own turn — see `refuse` in [Sessions from the command line](https://codeherder.com/docs/session-cli/). That blocks the task immediately, with the agent’s own reason (if it gave one) and a link back to the session, and ends only that one session. It only takes hold if the task is still at the stage that session was running — one that’s already moved on isn’t affected.
- **A send-back edge into `code` hit its round cap** — `review → code`, `verify → code`, or `merge → code`. See [How work flows](https://codeherder.com/docs/how-work-flows/) for how the round cap works and how to change it.
- **The task’s spend reached a cost cap.** See [Spend limits](https://codeherder.com/docs/spend-limits/).
- **Sessions kept failing to make progress on the selected device** — failing to start, ending again and again without moving the task forward, or an agent that keeps refusing. CodeHerder parks the task once it’s tried enough times, and the blocker note names which limit was hit. See [Why isn’t my task moving?](https://codeherder.com/docs/task-not-moving/) for the device-health causes this usually traces back to.
- **A parent task’s children didn’t all succeed.** A task created to fan out and wait for all its children (`ch task create --join-policy all`) is blocked when a child finishes at an end stage that doesn’t count as success — the note says how many failed. A cancelled or archived child is left out: it neither blocks the parent nor counts toward completing it. None of CodeHerder’s built-in task types have a non-success end stage, so you’d only meet this on a customized workflow. See [Understanding the task hierarchy](https://codeherder.com/docs/hierarchy/) for how a container task tracks its children.
- **Someone moved the task to `blocked` by hand** — from the web app’s status dropdown, or `ch task status <taskId> blocked`. CodeHerder files a note recording which stage the task was at.
- **A merge couldn’t land and needed something no agent can grant** — most often a required human approval on the merge request. The merge agent files this itself rather than leaving the task stuck at `merge`. See [How work flows](https://codeherder.com/docs/how-work-flows/) for the other ways a stuck merge gets resolved.

A **dependency** note works differently: the task’s status doesn’t change — it stays wherever it was, normally `todo` — but CodeHerder still holds it out of the staffing queue until the upstream task finishes. See [Which blockers need you](https://codeherder.com/docs/blockers/#which-blockers-need-you) below for what, if anything, you need to do about one of these.

Filing a blocker yourself on a task that has already reached `done`, `cancelled`, or `archived` is rejected outright — a finished task can’t be paused.

### Notes that don’t change the task’s status

A workflow stage built to wait for an external merge request can carry its own automatic note alongside the task’s regular blockers, without ever moving the task to `blocked`. It shows up in three cases: the merge request was closed without merging, CodeHerder couldn’t check the merge’s status, or the wait ran past its own limit. It clears itself once the merge lands, the merge request reopens, or status checks start working again — nothing to do by hand. None of CodeHerder’s built-in task types have a stage like this; you’d only meet one on a workflow customized to add it. See [Integrations](https://codeherder.com/docs/integrations/) for how CodeHerder reads a merge request’s status from GitHub or GitLab.

## Which blockers need you

Reading a blocker tells you whether it clears itself or needs you to act.

**Clears on its own:**

- A dependency note, as soon as the named upstream task finishes — see below.
- A blocker from an agent question, the moment you answer it — see [When an agent needs your input](https://codeherder.com/docs/agent-input/).
- A blocker filed with a release time, once that time passes — see [Blockers that release themselves](https://codeherder.com/docs/blockers/#blockers-that-release-themselves).
- A park for sessions failing to start on one device, as soon as that device recovers — see [Automatic recovery](https://codeherder.com/docs/blockers/#automatic-recovery) below.
- A merge-wait note, once the merge lands or its status can be read again — see [Notes that don’t change the task’s status](https://codeherder.com/docs/blockers/#notes-that-dont-change-the-tasks-status) above.
- A park for repeated session failures, once, after a person comments on the task or answers one of its agent questions — see [One retry after you comment](https://codeherder.com/docs/blockers/#one-retry-after-you-comment) below.

**Needs you to resolve by hand:** a reject-loop round cap, a spend cap, a join failure, an agent’s refusal, a manual block, a blocker filed with no release time, and a park with no retries left. Resolve any of these as described in [Resolving a blocker](https://codeherder.com/docs/blockers/#resolving-a-blocker) below.

A dependency note clears as soon as the named upstream task finishes — including one that’s cancelled or archived instead of completed: the note still clears, and CodeHerder separately tells you the upstream’s work never landed (see [When an upstream is cancelled or archived](https://codeherder.com/docs/collaborating/#when-an-upstream-is-cancelled-or-archived)). There’s nothing for you to do here.

One more thing worth knowing if you’re actively building on a task: adding a dependency to a task that’s already underway pulls it back. CodeHerder retires its live session, discards that session’s in-progress work, and reverts the task to its starting stage so the dependency gate can govern when it’s allowed to proceed again. Add dependencies before work starts where you can, and see [Task dependencies](https://codeherder.com/docs/collaborating/#task-dependencies) for the full dependency model.

### Automatic recovery

When sessions keep failing to start on one device, CodeHerder clears the resulting blocker as soon as that device is healthy again. If nothing else holds the task, it goes back to its stage within a few minutes. This covers start failures only; a park for sessions that ended without moving the task forward stays until you resolve it. A workspace owner or admin can also clear a start-failure park without waiting for the device to recover, and so can the device’s own owner as long as they still hold admin rank in the workspace: `ch device clear-provision-breaker <device>`. The task again returns to its stage within a few minutes. See [Managing your devices](https://codeherder.com/docs/devices/) for what makes a device healthy.

### One retry after you comment

After CodeHerder parks a task for repeated session failures, a comment from a person on the task gives it one automatic retry — so does a person answering one of its open agent questions. An agent’s own comment doesn’t count. CodeHerder checks for this roughly every five minutes, and the retry spends from the same budget rather than resetting it, so a task that fails again afterwards can park again sooner. This doesn’t apply to the reject-loop round cap — that one always needs a person to resolve it by hand.

## Where you meet a blocker

**On the task itself:** a banner appears at the top of the task’s page whenever it has any open blocker. That includes a dependency note on a task that isn’t `blocked` — the wait is still worth knowing about, even though the status hasn’t changed. The title names which kind you’re looking at: “Blocked — waiting on another task” for a dependency, or “Blocked — an agent is waiting on a human” for anything else. Below it, a line says where the blocker came from — who raised it, and during which stage’s session, if it came from one — or, for a dependency, “Filed automatically — this task’s dependency is not met yet.” With more than one blocker open, the banner also shows the count and a link down to the full list.

Resolve it right there, or scroll down to the **Active blockers** section for the full list and the filing form. A task that has ever carried a blocker also keeps a separate **Resolved blockers** section below it, once at least one has been cleared.

**The Blockers page** and **your My work view** both list blockers across the workspace — see the sections below.

## Blockers that release themselves

Any blocker you file can carry a release time — a future instant after which CodeHerder resolves it for you. It’s useful for a cool-down period, a wait on a vendor maintenance window, or simply “don’t touch this again until Monday” — anything where you already know when the hold should end, so there’s no reason to make anyone come back and clear it by hand.

**Web app:** the filing form has an optional **Auto-release at** field — a date and time picker, read in your own time zone.

**CLI:** add `--until` or `--for` to `ch task block`, mutually exclusive:

```
ch task block <taskId> --reason "Waiting out the maintenance window" --until 2026-09-01T09:00:00Z
ch task block <taskId> --reason "Cooling down before retry" --for 24h
```

`--until` takes a full timestamp, or a bare date (`2026-09-01`) — a bare date resolves to midnight UTC, so if you mean a specific time of day in your own zone, spell out the full timestamp instead. `--for` takes a duration counted from now, for example `30m` or `24h`. There’s no day unit, so write a week as `168h`, not `7d`. Either way, the release time has to be in the future; CodeHerder rejects the blocker outright if it isn’t.

When the release time arrives, CodeHerder resolves the blocker for you — it’s checked roughly every five minutes, not to the second. If the blocker had moved the task to `blocked` and nothing else is holding it, the task returns to its stage automatically, the same as resolving it by hand would. An auto-released blocker shows no one as the resolver, since you didn’t act on it — just the time it cleared.

Filing a blocker with a release time doesn’t stop you from resolving it early — see [Resolving a blocker](https://codeherder.com/docs/blockers/#resolving-a-blocker) below. There’s no way to change a release time once it’s set; resolve the blocker and file a new one with the time you actually want.

## The Blockers page

The **Blockers** link stays in the sidebar at all times, with a pulsing red badge showing a count — the badge is absent when that count is zero. The count is scoped to blockers that need a person: it doesn’t include unmet-dependency notes, so a task quietly waiting on an upstream never turns it red.

Above the list, two filter pills narrow what shows: **Active** hides already-resolved blockers, and is on by default; **Unmet dependencies** brings dependency notes into view, and is off by default. Together, the page opens showing only unresolved, person-facing blockers. A search box next to the pills matches on the reason text, the task’s title, or who filed it. A **clear filters** control resets the pills and search in one click, and a **Showing N of M** count above the table tracks how much of the filtered list is loaded — scroll down and it keeps loading more.

Click **Task**, **Filed by**, or **Created** to sort by that column; unsorted, the list reads newest first. Each row shows:

- **Task** — the affected task; click the row to open it.
- **Reason** — the blocker’s reason. For a dependency note this reads as “Waiting on” the upstream task’s own title, linked straight to it; for anything else it’s the free-text reason. A line underneath can show the release time (only while the blocker is still open) and, separately, a linked upstream task — a blocker can carry both at once.
- **Filed by** — who or what filed it.
- **Created** — when it was filed.

A row whose own task has since been cancelled carries a **Task cancelled** badge; a dependency row carries an **Unmet dependency** badge. An action column offers **Resolve** on an active row; a resolved one instead shows when it was cleared, and by whom — except one that released itself automatically, which shows only the time, since nobody acted on it.

Your personal **My work** view also lists the workspace’s open, person-facing blockers with the same **Resolve** action — see [My work](https://codeherder.com/docs/my-work/).

## Filing a blocker yourself

**Web app:** open the task, find the **Active blockers** section, and click **File blocker** to open the filing form. Alongside the reason, it has an optional **Auto-release at** field — see [Blockers that release themselves](https://codeherder.com/docs/blockers/#blockers-that-release-themselves) above. To link a specific upstream task to the blocker, use the CLI form below.

**CLI:**

```
ch task block <taskId> --reason "Waiting for the vendor API contract" [--blocked-on <upstreamTaskId>] [--until <timestamp>|--for <duration>]
```

`--reason` also accepts `--reason-file <path>` or a piped-in body for longer notes. Filing a blocker moves the task straight to `blocked` from whatever active stage it was in.

`--blocked-on` names a related task for a reader’s benefit — it’s a note, not a gate. Filing this way does **not** make the blocker clear itself when the named task finishes; a person still has to resolve it by hand. If what you actually want is “hold this task until that one finishes, and release it automatically when it does,” use a real dependency instead:

```
ch task depends-on <taskId> <upstreamTaskId>
```

See [Task dependencies](https://codeherder.com/docs/collaborating/#task-dependencies) for the full dependency model.

## Viewing a task’s blockers

```
ch task blockers <taskId>                              # every blocker on the task, active and resolved
ch task blockers <taskId> --active                     # only unresolved blockers
ch task blockers <taskId> --limit N --cursor <token>   # default 200, max 500
```

The table lists **STATE**, **ID**, **FILED-BY**, **BLOCKED-ON**, **RELEASES-AT**, and **REASON** — in that order. **BLOCKED-ON** names the linked upstream task when the blocker has one, or `-` when it doesn’t. **RELEASES-AT** shows the release time for a blocker that has one, in your own time zone, and `-` for one that doesn’t. **REASON** truncates long text — for the full reason, read the task’s detail page in the web app, or add `--json` to the command above. For an automatic dependency note, the CLI’s REASON column shows CodeHerder’s own internal tracking text rather than a friendly sentence; **BLOCKED-ON** already names the upstream task directly, so read that column instead. See [Paging through long lists](https://codeherder.com/docs/using-the-cli/#paging-through-long-lists).

## Resolving a blocker

Pass the **blocker’s own ID** from the list above — not the task ID:

```
ch task unblock <blockerId>
```

Any workspace member can resolve a blocker by hand at any time — including one with a release time still ahead of it, resolving it early rather than waiting for that time to pass. Resolving one already resolved is refused, not a no-op — check `ch task blockers <taskId>` if you’re not sure it’s still open. For a blocker that moved the task to `blocked` (see [How a task gets a blocker](https://codeherder.com/docs/blockers/#how-a-task-gets-a-blocker) above), resolving the last active one automatically returns the task to the stage it was at when it was blocked, and work resumes with no further action. If CodeHerder can’t determine that stage, the task stays at `blocked` — move it forward yourself:

```
ch task status <taskId> <stage>
```

Moving a task out of `blocked` by hand this way resolves every blocker still open on it, not just the one you had in mind — useful if you know the underlying problem is fixed and don’t want to track each one down individually.

A dependency note doesn’t return a task from `blocked`, since the task’s status never changed in the first place — see [Which blockers need you](https://codeherder.com/docs/blockers/#which-blockers-need-you) above for what actually has to happen before the task can proceed.

`ch task block`, `ch task blockers`, and `ch task unblock` above are frozen aliases for the nested `ch task blockers` child collection (`create` / `list` / `resolve`) — see [Managing a record’s child collections](https://codeherder.com/docs/using-the-cli/#managing-a-records-child-collections).

## Who gets notified

When a blocker moves a task to `blocked`, CodeHerder messages the task’s owner — unless the owner is the one who filed it. It also messages anyone watching the task for status changes. Mentioning someone in a blocker’s reason (`@name`) notifies them too, the same as an @-mention anywhere else on a task. See [Watching tasks and notifications](https://codeherder.com/docs/watching/) for what a watcher actually receives and how to change it.

## Related guides

- [Why isn’t my task moving?](https://codeherder.com/docs/task-not-moving/) — start here to diagnose a stuck task, including causes beyond blockers
- [Collaborating](https://codeherder.com/docs/collaborating/) — task dependencies, the model this page’s dependency notes are built on
- [When an agent needs your input](https://codeherder.com/docs/agent-input/) — the question-and-answer flow that can also block a task
- [Sessions from the command line](https://codeherder.com/docs/session-cli/) — the full `refuse` flow, and everything else you can do to a session
- [Managing your devices](https://codeherder.com/docs/devices/) — device health, and what clears a device-recovery park
- [How work flows](https://codeherder.com/docs/how-work-flows/) — the reject-loop round cap and stage pipeline
- [Spend limits](https://codeherder.com/docs/spend-limits/) — cost caps that can block a task
- [Watching tasks and notifications](https://codeherder.com/docs/watching/) — who gets told about a blocker, and how to change it
- [My work](https://codeherder.com/docs/my-work/) — your personal queue of blockers waiting on you
- [Review debt](https://codeherder.com/docs/review-debt/) — how often the reject-loop cap actually fires across the workspace
