CodeHerderSearch⌘KRequest access →

← All docs

Why isn't my task moving?

A diagnostic guide for tasks that appear stuck — covering approvals, blockers, spend caps, stalled sessions, staffing gaps, host-CLI auth failures, task types, container tasks, and reopened tasks that never restart.

When a task sits in the same status longer than expected, start here. Most causes leave a visible signal — the quickest first step is always to read what the task is actually showing you:

ch task show <taskId>

The output surfaces the current status (with a pointer to ch task blockers when that status is blocked), a next stages row listing every stage the task can move to right now, any pending-approval banner, whether the task is stalled, and the last time the task was touched. Match what you see to one of the causes below.

1. Waiting on an approval

Signal: ch task show <taskId> shows a ⏸ pending advance → <stage> banner. The task’s status has not changed, but an advance request is waiting.

What happened: CodeHerder tried to advance the task on its own and hit an approval gate — a rule that requires a designated member to sign off before the advance goes through. The request is recorded but held until an eligible approver acts. This only ever happens on a stage CodeHerder can leave automatically — for the built-in feature, bug, and story types, that’s the todo stage a task starts at, before any agent has picked it up.

What to do: If you are an eligible approver, approve it:

ch task approve <taskId>

To see all pending advances waiting on you across the workspace:

ch task list --awaiting-approval

If you are not the right approver, share the task with whoever is. If the work shouldn’t start yet, reject it instead:

ch task reject <taskId>

For how approval gates are configured and all the approval actions, see Approvals & staying in control.

2. Blocked

A task moves to the blocked status several ways: someone filed an explicit blocker, an agent posted a question it couldn’t answer alone, the task hit the automatic reject-loop cap, or the task’s spend reached a cost cap. An unmet dependency works differently and does not change the task’s status — see Unresolved dependency below.

Filed blocker

Signal: ch task show <taskId> shows status blocked (with a pointer to ch task blockers).

What to do: List the active blockers, then check whether any already has a release time — ch task blockers <taskId> --active shows it in the RELEASES-AT column. A blocker with one clears itself once that time passes, so there’s nothing to do but wait. For anything else, resolve it by hand:

ch task blockers <taskId> --active
ch task unblock <blockerId>

Pass the blocker ID (shown in the blockers list) to ch task unblock, not the task ID. When the last active blocker is resolved, CodeHerder automatically returns the task to the stage it was at when it was blocked.

If the task stays at blocked after you resolve the blockers, move it back manually. ch task show <taskId>’s next stages row names the stage it was blocked at, so you don’t have to guess:

ch task status <taskId> <stage>

For the full blocker lifecycle, see Blockers and blocked tasks.

Agent question

Signal: The task is blocked with a reason of “Waiting for human answer to agent question”. The question shows up as an open card on the Dashboard, and on the task’s own page too.

What to do: Answer the question from wherever you’re looking at it — the Dashboard, the task page, or anywhere else it appears — see When an agent needs your input for the full list. Submitting your answer clears the blocker and resumes the task automatically; there’s nothing to unblock by hand.

Unresolved dependency

Signal: The task sits at todo and never advances, even though nothing else looks wrong — its status is not blocked. ch task blockers <taskId> --active shows a blocker whose reason starts with unmet dependency:; that blocker, not the task’s status, is what’s holding it back. Check its upstream tasks:

ch task deps <taskId>

What to do: Every upstream dependency must finish before this task can advance — planning stages included, not just build stages. If an upstream is simply still in progress, no action is needed: once every upstream finishes, the blocker clears and the engine resumes this task automatically. If an upstream isn’t going to finish on its own, cancelling it (with a reason) or removing the dependency edge both clear this task to proceed, since cancelling counts as finishing. If the task no longer makes sense without whatever that upstream would have delivered, cancel this task too rather than let it run without it.

For how dependencies work, see Collaborating and Blockers and blocked tasks.

Reject-loop cap reached

Signal: The task was sent back between review and code, or between verify and code, enough times to hit the configured round cap. The engine moves it to blocked automatically and posts a blocker note explaining what happened.

What to do: A human needs to review the situation. Read the blocker note with ch task blockers <taskId>ch task show <taskId> only points you at that command, it doesn’t print the note itself — decide whether to resume the task at the indicated stage or cancel it, then act accordingly:

ch task status <taskId> <stage>           # resume
ch task status <taskId> cancelled --reason "Superseded by …"  # cancel

For how the reject-loop cap works and how to change it per task type, see How work flows.

Verification keeps failing

Signal: The task leaves code, reaches review or verify, then returns to code on its own — possibly more than once — with no manual send-back.

What happened: the stage’s agent recorded a fail for a required verification, so CodeHerder routed the task back to code on its own for another build attempt. This is the same reject loop as above, just triggered by a recorded result rather than by a human reviewer. For a full explanation of what a verification is and which ones the built-in types declare, see the Stage verifications section of How work flows.

What to do: Usually nothing — the workflow is self-correcting. Read the hand-off comment on the task to see what failed:

ch task show <taskId>

To see exactly which check failed, list every verification result recorded for the task:

ch task verifications <taskId>

The output is a table listing the stage and check, its status, which build attempt it ran on, and any recorded detail (- when there’s none) — with the most recent result listed first. <status> is one of pass, fail, error, or skipped. Omit <taskId> and it defaults to the task set in your CH_TASK_ID environment variable.

Each time a task returns to code this way, CodeHerder escalates to a more capable model for the next attempt — see Automatic model escalation on rework in How work flows. If the task keeps bouncing, it eventually hits the round cap and moves to blocked. At that point, follow Reject-loop cap reached above.

Spend cap reached

Signal: ch task blockers <taskId> --active shows a blocker whose reason starts with cost budget exceeded.

What happened: The task’s total spend reached a cost cap — either your account’s default task-total spend cap or an explicit budget set on the task — so CodeHerder stopped its active sessions and moved it to blocked.

What to do: Raise the applicable cap first, or the task will be blocked again the next time CodeHerder checks. Open Settings → Plan → Spend caps and raise or clear the Task total spend cap (default) field, then resolve the blocker:

ch task blockers <taskId> --active
ch task unblock <blockerId>

For how spend caps work and where to change them, see Spend limits.

3. Stalled assignee

Signal: ch task show <taskId> shows a stalled: row and a last touch: row.

What happened: The task’s current assignee has not touched it for long enough that CodeHerder flagged it as stalled. The task is still assigned; it is just not progressing.

What to do: Touch is the working agent’s own liveness ping for the stage it’s currently running — it isn’t something you run as a human, and doing so returns an error. Instead:

  • Comment on the task. Posting a comment is itself confirmed activity and clears the stalled flag right away.
  • Move it to its next stage, if it’s ready to advance.
  • If the current assignee genuinely can’t continue, reassign the stalled stage from the task’s workflow stage panel in the web app — open the task, find the Assignee dropdown for that stage, and choose a different agent or person.

How long a task can sit untouched before it’s flagged is a per-workspace setting (60 minutes by default) — see Editing a workspace in Managing workspaces. For the full stall flow, see Assigning and claiming work.

4. No agent or device available to staff the task

An eligible, ready task can still sit in todo if the staffing prerequisites are not met. Check the following:

No repository resolves to build in

Signal: An auto-staffed task never leaves its first stage. The task’s Sessions view fills with sessions that are abandoned almost immediately, without ever reaching a running agent, and — unlike a device or host-CLI problem — no error reason is shown against them. Left long enough, the task is eventually flagged as stalled — in ch task list, its status shows a (stalled) suffix.

What happened: Every build session is cut from one repository. CodeHerder resolves it automatically: first a default repo you’ve set on the workspace, and if none is set, the workspace’s sole active (non-archived) repository registered directly on it. Without a default, a workspace with no repository of its own, or more than one, leaves the engine unable to tell which one the task should use, so the session is abandoned before it starts. This particular failure records no visible reason, which is why the task simply never moves.

What to do: Set a default repo so CodeHerder always knows which one to use:

ch workspace edit <workspace> --default-repo <name|id>

This also works if the repository you want is only inherited from a parent group — pointing the default at it is enough, no need to register a second copy directly on the workspace. If you’d rather not set a default, keep exactly one active repository registered directly on the workspace and archive the rest with ch repo archive <name>. ch repo list mixes repositories registered directly on the workspace with ones inherited from a parent group and gives no way to tell them apart; open Set up → Repos in the web app if you need to check, where an inherited repository carries an inherited badge. For the full routing rules, see Which repository a task builds in in Connecting repositories.

Device is Offline

Signal: The task is unassigned and waiting; your devices are Offline.

ch device list

What to do: Start or restart ch device-server on the machine. The device comes Online as soon as the server connects and the engine resumes placement. If you have no device registered yet, see How do I add a device? for the first-run steps.

Agent not assigned or not approved

Signal: The device is Online but not picking up work.

An agent must be both assigned to the device and approved before the device can run it. Check the agent’s assignments under Set up → Agents and Set up → Devices in the sidebar. If either step is missing:

ch agent assign <agentId> <deviceId>
ch agent approve <agentId> <deviceId>

Approving is enough on its own — a connected device picks up the assignment immediately. If the device is Offline, start ch device-server and the approval lands as soon as it connects.

Secret reference unacknowledged or unresolvable

Signal: The device is Online, assigned, and approved, but a session for this agent still won’t start. The task shows that the agent failed to start, and the reason mentions a secret reference the device owner hasn’t acknowledged for this assignment, or a secret reference that couldn’t be resolved on the device.

What happened: If the agent’s launch config references a device-side credential (an @secret:<key> value, rather than a workspace secret), every referenced key must be explicitly acknowledged by the device owner or a workspace owner, and the value itself must actually be stored on that device. A session refuses to start if either is missing — including after you add a new @secret: reference to a config that was already approved.

What to do: Re-approve the assignment listing every referenced key:

ch agent approve <agentId> <deviceId> --secret <key>

If the failure instead says a secret reference couldn’t be resolved on the device, it won’t name the key — check which @secret:<key> references your top launch config uses and confirm each one is stored on that device. See Secrets on a device for both steps end to end, including the one gap in the web app’s approval panel that trips people up here.

Device at capacity

Signal: The device is Online and agents are assigned, but the device is fully busy.

Check the Capacity panel under Set up → Devices → [the device] for its Max concurrent sessions limit — see Concurrency and capacity for how that combines with the platform-wide ceiling. If the device is running at its limit, new tasks queue automatically and start as soon as a running session finishes. Raise the limit in the same panel if you want more parallel work.

If the device stays full even though nothing seems to actually be running there, some session-worktree slots may be left over from sessions that already ended rather than genuinely in use. Expand the device’s row and check the Worktree slots check in the Health snapshot panel — if it reports orphaned or reclaimable slots, force an immediate clean-up instead of waiting for the next automatic pass:

ch device reclaim <deviceId>

See Reclaiming stuck worktree slots in Managing your devices for the full picture, including who can run it and what to check before you do.

Unhealthy device

Signal: The device is Online and has free slots, but a task still does not start. In Set up → Devices, the device row shows a red unhealthy health badge and a grey not staffable chip. If a session was attempted and failed, the task comment notes that the device is unhealthy and names the failing readiness check.

What happened: CodeHerder runs readiness checks on every device and withholds new sessions from any device whose health rolls up to unhealthy — meaning at least one blocking check has failed. Common causes include git credentials expiring, the session-root directory becoming unwritable, disk space dropping critically low, or a coding CLI that’s installed but not signed in — see Coding agent not signed in below, the most common cause on a newly registered device.

What to do:

  1. In Set up → Devices, click the device row to expand it.
  2. The Health snapshot panel lists every failing check with a short summary and a remediation tip.
  3. Fix the issue on the device — for example, re-run gh auth setup-git if a git-credentials check is failing, or free disk space if disk headroom is critically low.
  4. The device re-checks automatically within roughly five minutes (or immediately if you restart ch device-server). Once the check passes, the not staffable chip disappears and the engine can place new sessions on the device again.

For the full list of readiness checks and what each one verifies, see Device health and readiness checks in Managing your devices.

Coding agent not signed in

Signal: The device is Online but a task assigned to it won’t start. In Set up → Devices, the device row shows a red unhealthy badge and a grey not staffable chip. Expanding the row, the Health snapshot panel shows the Harness auth check failing. If a session was attempted anyway, it dies at startup unable to reach a model.

What happened: A coding CLI can be installed on a device but not signed in to its AI provider — installing it only satisfies the Harness ready check. A separate Harness auth check confirms at least one installed CLI is actually signed in, and blocks new work until it is. This is the most common cause of an unhealthy, non-staffable device right after you register it.

What to do:

  1. On the device, sign in with that harness’s own login command — claude login, codex login, cursor-agent login (or set CURSOR_API_KEY), or opencode auth login, matching the CLI the agent’s config uses. This check can only verify those four harnesses — Pi has no headless sign-in probe, so a Pi-only device never shows a Harness auth row at all; see Choosing the coding-agent CLI your agents run.
  2. The device re-checks automatically within roughly five minutes, or immediately if you restart ch device-server.
  3. Once the check passes, the not staffable chip disappears and the engine resumes placing work on the device.

For the full readiness-check reference, see Device health and readiness checks in Managing your devices.

Device is drained (paused)

Signal: The device is Online, healthy, and not at capacity, but tasks still are not placed there. ch device list shows drained in the DRAINED column, or ch device show <deviceId> prints drained: yes (since <when>).

What happened: Someone paused the device — with Disable in the web app or ch device drain <deviceId> on the CLI — to stop new work from landing there without interrupting sessions already running. The device’s Online state and health badge do not change; only new placement is skipped.

What to do: Resume placement:

ch device undrain <deviceId>

Or click Enable on the device’s row in Set up → Devices. Placement resumes immediately.

For the full pause/resume workflow, see Pausing a device in Managing your devices.

Subscription usage window exhausted

Signal: The device is Online, healthy, not at capacity, assigned, and approved — yet tasks still do not start. In Set up → Devices, the device detail page shows the AI limits section with at least one usage window at 100%.

What happened: CodeHerder monitors each device’s AI subscription usage. When a Claude subscription window reaches 100% utilization before it resets, the engine stops routing new tasks to that device to avoid stranding sessions on a provider that will reject them. Sessions already running on the device are not interrupted. The device’s Online and healthy status do not change — the only visible signal is the full usage meter in the AI limits section.

What to do: No manual action is required. Once the window resets and the device server checks usage again (typically within five minutes of the reset), the engine resumes placing tasks on the device automatically. The resets in text next to the usage meter shows how long to wait.

If the stage’s Assignee is on Auto — best fit and a second device with headroom is registered and staffed, CodeHerder already tries it automatically — no action needed. If the stage is pinned to a specific agent that only runs on the exhausted device, re-pin it to an agent on a different device from the Assignee dropdown, or wait for the window to roll over.

For the full explanation — why the pause is device-wide, why switching launch configs on the same device doesn’t help, and how failover across devices actually works — see AI usage limits.

Capability mismatch

Signal: The device is Online and has free slots, but the task still does not start.

See Capabilities for what a capability label is and the different rules for each place it appears.

There are two distinct causes that produce this signal:

No agent template covers what the stage requires. Workflow stages declare which capabilities an agent template must carry to run them — for example, a planning stage may require model:opus and a build stage may require model:sonnet. These are set on the template with --cap in ch agent config set, not at creation time. If no template in your workspace carries the needed capability, the stage is unstaffable: the task queues and never starts.

The web app surfaces this as a Capability gap callout at the top of the Set up → Agents page, naming exactly which capability is missing. To fix it, add the capability to an existing template:

ch agent config set <agentId> --command <command> --cap <capability>

Include all the flags you want to keep (persona, args, env vars) — config set replaces the stored template. If you need a brand-new template for this role, create the agent first with ch agent create, then set its caps with ch agent config set.

For the Capability gap callout and how to read the Agents page, see Monitoring your agents. For creating and configuring templates, see Agents and the CLI.

The agent needs tooling the device does not have. An agent template can specify that the host device must have certain tools installed — for example, the AI coding binary or the git-host CLI (gh or glab). Devices detect their available tooling automatically. If the device is missing a tool the agent requires, the engine cannot place a session there.

What to do: Install the missing tool on the device. The device picks up the change the next time it connects and placement can proceed.

For the full device management and capability reference, see Managing your devices and Agents and the CLI.

5. Stuck at code or merge — host-CLI not authenticated

Signal: The task is stuck at the code or merge stage. Looking at the task’s Sessions view shows a failed session with an error about the git-host CLI (gh or glab) not being found or not authenticated.

What happened: Agents open and land pull requests through the git-host CLI installed on the device. If that CLI is missing or its authentication token has expired, the code-stage agent cannot open the pull request, or the merge-stage agent cannot land it — whichever stage runs first fails and posts the error as a task comment.

What to do: On the device that ran the failed session, confirm which CLI is involved and its current state:

ch githost auth-check

The command detects the host from the repository’s origin remote and prints a clear result — when authenticated, when not. For a specific repository, supply its remote URL:

ch githost auth-check --remote https://github.com/acme/my-app

Note: ch githost auth-check supports github.com and gitlab.com only. If your repository is on Bitbucket or a self-hosted instance, the command does not apply — check the host CLI’s own auth status directly.

If the check fails, follow the printed remediation:

  • GitHub: gh auth login on the device.
  • GitLab: glab auth login on the device.

Once the CLI is authenticated, move the task back to the failed stage:

ch task status <taskId> code    # if it failed at code
ch task status <taskId> merge   # if it failed at merge

The engine staffs the task to the next available session. For the full device requirements — git credentials and host-CLI setup — see Connecting repositories.

6. A task-type task waiting for a human

Signal: The task type is task and the task sits at todo with no assignee.

What happened: The task type is intentionally not auto-staffed. It is the lightweight human-directed to-do — the engine leaves it for a person to direct.

What to do: Open the task in the web app and use the Assignee dropdown on the workflow stage to route it — select an agent, another person, or yourself. For a full walkthrough, see Assigning and claiming work. For why task works differently from other types, see How work flows.

7. A container task waiting on its children

Signal: The task type is epic or initiative, and the status is in_progress.

What happened: in_progress is the normal holding state for containers — it means “child tasks are being worked”. The container itself never runs a build stage; it stays at in_progress until every child task reaches done.

What to do: Check the state of the child tasks:

ch task children <taskId>

Progress the children through their own workflows. The container advances to done automatically once all children are complete. You cannot close the container manually until then.

For the container lifecycle and the planning stage that precedes in_progress, see How work flows and Core concepts.

8. A reopened task that never restarts

Signal: The task’s status is in_progress, its type is story, feature, bug, or task — but no session ever starts. There’s no blocker filed and nothing waiting on you; the task simply sits there looking active.

What happened: Reopening a finished task (from done, cancelled, or archived) moves it to in_progress. That’s the correct holding stage for an initiative or epic, but it isn’t a stage in the story, feature, bug, or task pipeline. The task looks active, but there’s no stage for an agent to work, so nothing gets staffed — and it can’t be moved back to plan or code from there either.

What to do: File a new task for the follow-up work, then close or cancel the reopened one — it isn’t going anywhere on its own. See Reopening a cancelled or completed task in Editing and cancelling tasks for the full explanation and the exact commands.

CodeHerder

Round up your herd.

Bring every human and every agent onto one table. Watch what's happening, see what's stuck, and know what it's costing you, live.

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

↑↓ move · ↵ open · esc close