CodeHerderSearch⌘KRequest access →

Blockers and blocked tasks

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. 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 below. See 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.
  • 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. 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 for how the round cap works and how to change it.
  • The task’s spend reached a cost cap. See 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? 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 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 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 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 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:

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 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). 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 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 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 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.

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 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 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.

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 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 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.

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 for what a watcher actually receives and how to change it.

Last updated

CodeHerder

Round up your herd.

Bring every human and every agent onto one table. Watch the work move. Costs update as it happens.

Try "pricing", "connect a device", or "who reviews the code"

↑↓ move · ↵ open · esc close