Why isn't my task moving?
A diagnostic guide for a task that looks stuck, and how to unstick it.
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 it’s blocked), a next stages row, an advance to <stage> row naming what’s blocking the next move, any pending-approval banner, whether the task is stalled, and the last touch time. Match what you see to one of the causes below.
Start with the advance row
Before working through the causes below, check the advance to <stage> row ch task show <taskId> prints. It’s the fastest way to learn whether a task can move on, and what’s missing when it can’t:
advance to review: blocked — artifact: required field is empty
The web app’s task page shows the same verdict below the stage breadcrumb, as an Advance to <stage> button or, in its place, a line naming what’s outstanding.
Act on what the row names:
- A field name and reason (like
artifact: required field is emptyabove): fill that field. See A required field is missing at the current stage, below. - An outstanding checklist: tick the remaining acceptance-criteria items. See Write acceptance criteria.
- A verification: record a result for it. See Verification keeps failing, below.
- A sentence about a dependency, unfinished child tasks, or a change-shape limit: see the matching cause below.
A row like this is never a stuck task — it’s an unmet requirement, and it already names which one. If ch task show prints no advance to row at all, the task has no next stage right now (terminal, or its pipeline ends here) — expected, not a symptom; look at the causes below instead.
What the row doesn’t cover
not blocked means the task meets the requirements for its next move — it doesn’t mean the task is about to move. The row reports requirement checks only, the ones in Before a task moves on in How work flows.
So a task can read not blocked and still go nowhere: a waiting approval, no available device, a signed-out host CLI, a stalled assignee, a spend cap, the reject-loop cap — none show up in the row. Each has its own section below.
The fastest way to find which one applies: let the activity feed name it directly — see Let the activity feed name the cause, next.
Let the activity feed name the cause
If the advance row says nothing is outstanding and the task still isn’t moving, don’t stop there — the activity feed usually names the cause in a plain sentence:
ch activity task <taskId> --type task.staffing_skipped,task.start_refused
The WHY column carries that sentence for a staffing-skip or start-refusal row — empty when no reason was recorded, the raw value when your ch doesn’t yet know it. The --type filter cuts the per-turn cost rows this feed otherwise mixes in. See Activity feeds for the full column reference.
In the web app, the task page’s Activity section shows the same sentence as a row’s detail, but not always by default: a staffing-skip sentence is on the curated story; a start-refusal sentence needs the All events toggle in the section’s header. The CLI shows both with no toggle — check it, or flip the toggle, before assuming nothing was recorded.
A few sentences name a read that failed for this sweep only (a count, a config, the task row) or a session create that didn’t take. These retry automatically; act only if one keeps recurring for the same task.
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 and hit an approval gate — a rule requiring a designated member to sign off first. The request is recorded but held until an eligible approver acts. This only 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.
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, including someone filing an explicit blocker, an agent posting a question it couldn’t answer alone, the task hitting the automatic reject-loop cap, or its spend reaching a cost cap — see How a task gets a blocker for the full list. 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 the RELEASES-AT column — a blocker with a release time 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 (from the blockers list), not the task ID. Resolving the last active blocker returns the task automatically to the stage it was at.
If it stays at blocked anyway, move it back manually — ch task show <taskId>’s next stages row names the stage:
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”, shown as an open card on the Dashboard and on the task’s own page.
What to do: Answer it from wherever you’re looking at it — see When an agent needs your input for the full list. Submitting clears the blocker and resumes the task automatically; 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 status, is holding it back. Check upstream tasks:
ch task deps <taskId>
The Tasks page’s Graph view lays out the whole upstream chain at once.
What to do: Every upstream must finish before this task can advance — planning stages included. If one is still in progress, no action is needed: once all finish, the blocker clears automatically. If an upstream won’t finish on its own, cancelling it (with a reason) or removing the dependency edge both clear the way, since cancelling counts as finishing. Cancel this task too if it no longer makes sense without that result.
See Collaborating and Blockers and blocked tasks. The advance to <stage> row above already names an unmet dependency.
Reject-loop cap reached
Signal: The task was sent back into code from review, verify, or merge enough times to hit the round cap on one of those routes. The engine moves it to blocked automatically and posts a blocker note explaining what happened.
What to do: A human needs to review this. Read the blocker note with ch task blockers <taskId> (ch task show <taskId> only points at that command), then fix the blocker and resume the task at the stage that fits, or cancel it:
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 workflow, 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 for another build attempt — the same reject loop as above, just triggered by a recorded result rather than a human reviewer. See the Stage verifications section of How work flows. If a task is simply sitting with no pass recorded yet, rather than bouncing, the advance to <stage> row in Start with the advance row above names the missing verification.
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 lists stage, check, status, build attempt, and any recorded detail, most recent first. <status> is one of pass, fail, error, or skipped. Omit <taskId> and it defaults to CH_TASK_ID.
Each return to code this way escalates to a more capable model for the next attempt — see Automatic model escalation on rework in How work flows. If it keeps bouncing, it eventually hits the round cap and moves to blocked — follow Reject-loop cap reached above.
A result is refused
Signal: ch task verify is refused with a message that a required verification pass may not come from the party it checks, and the advance to <stage> row still names the verification.
What happened: CodeHerder keeps the checker apart from the author on two kinds of check. It refuses a required pass on the verify goal gate from an author, unless that member worked the verify stage itself. It refuses a judged check from anyone who worked that stage, or who already recorded a result on this attempt. A test, lint, or typecheck check has no such rule. See Who can record a pass in How work flows.
What to do: Pick the case that fits:
- For the
verifygoal gate, have a person or agent who did not build the change record thepass. For example, a workspace member who did not work on the task runsch task verify <taskId> goal_gate pass. - For a judged check, have someone who did not work that stage record the result.
The refusal message may suggest --execute. That helps only when the check’s stage declares a command. The built-in goal gate declares none.
Spend cap reached
Signal: ch task blockers <taskId> --active shows a blocker whose reason starts with cost budget exceeded (task) or cost budget exceeded (workspace).
What happened: Either the task’s own spend, or its workspace’s, reached a cost cap, so CodeHerder stopped its active sessions and moved it to blocked. Those sessions end Released, not failed — see Sandbox state vs. a run’s outcome.
What to do: Read the word in parentheses before raising anything — it names which cap applied; raising the wrong one leaves the task blocked. See Reading the blocker in Spend limits. Once raised or cleared, resolve the blocker:
ch task blockers <taskId> --active
ch task unblock <blockerId>
3. Stalled assignee
Signal: ch task show <taskId> shows a stalled: row and a last touch: row.
What happened: Whoever — or whatever — is working the current stage hasn’t touched it for long enough that CodeHerder flagged it as stalled. The stage is still being worked; it’s just not progressing.
Looking across the whole workspace? Run ch task list --stalled, or turn on the Stalled filter on the Tasks page.
What to do: Touch is the working agent’s own liveness ping — not something you run as a human; doing so returns an error. Instead:
- Comment on the task — posting is itself confirmed activity and clears the flag right away.
- Move it to its next stage, if it’s ready to advance.
- Pinning a different assignee from the Assignee dropdown doesn’t clear a stall by itself, and on a built-in type doesn’t even change which agent runs it next — see The Assignee dropdown — what it does today in Assigning and claiming work first.
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. To spot a coverage gap for your whole workspace before it strands the next task, see Staffing coverage.
Account is at its concurrent-session limit
Signal: The activity feed’s cause names the account as already at its session limit.
What happened: Your plan caps how many agent sessions run across the whole account at once, regardless of how many devices you have free.
What to do: ch workspace plan shows the concurrent agents line against the limit. Wait for a slot to free, or raise the plan’s concurrency limit — see Plans and limits. This differs from Device at capacity, below, which caps one device, not the account.
A session is already live for this task
Signal: The activity feed’s cause names an already-live session for this task.
What happened: Only one session runs a task at a time. CodeHerder re-checks this on every staffing sweep, so seeing it once for a task that’s genuinely being worked is expected, not a symptom.
What to do: Open the task’s Sessions view to confirm a session is running. If the task also looks stalled (see Stalled assignee above) and you’re confident the session is dead, stop it directly, then a fresh one can start:
ch session stop <sessionId>
See Following a live agent session for telling a running session from one gone quiet.
No repository resolves to build in
Signal: The task’s status is blocked, and ch task blockers <taskId> shows a note naming one or more candidate repositories.
What happened: CodeHerder resolves what a task builds in automatically — the task’s own attached repo set first, then a workspace default, then the one active repository across the workspace and its parent groups — and blocks the task right away when none of those resolve. This most often happens when a workspace has more than one active repository and neither a task-level repo set nor a workspace default names the one to use; the blocker note lists every repo it found in play. See Tasks that span several repositories and Which repositories a task builds in in Connecting repositories for the full resolution order.
What to do: Either attach a repo set to the task —
ch task repos create <taskId> <repo>
— or set a default for the workspace so future tasks resolve without you having to name one each time:
ch workspace edit <workspace> --default-repo <name|id>
This also works if the repository is inherited from a parent group. If you’d rather not set a default, keep exactly one active repository across the workspace and its parent groups, and archive the rest with ch repo archive <name>. Take care with a repo that belongs to a parent group: archiving it removes it from every workspace under that group. Set a workspace default instead when the extra repo is inherited.
A task blocked here has no repo attached, so either fix works: a new default or an attached repo applies the next time CodeHerder starts a session for the task. A task that already got a repo when it was created keeps that repo. A later change to the default doesn’t move it.
A similar note appears when a repository has been archived: the task’s set, or the workspace default, names a repo that’s no longer active, and CodeHerder won’t start a session for the task. The note names the repo. Bring it back with ch repo restore <name>, remove it from the task with ch task repos delete <taskId> <repo>, or change the workspace default with ch workspace edit <workspace> --default-repo <name|id> (or --clear-default-repo).
A related problem lands a task in the same blocked state: more than ten repos attached — a sandbox can only provision ten at once, so declaring more blocks the task with a note listing them all. Trim with ch task repos delete <taskId> <repo>.
A repo that fails to clone is different, not an immediate block — the sandbox fails to provision, the failure shows in the task’s Sessions view, and CodeHerder retries automatically before giving up. In a multi-repo set, the whole sandbox is abandoned and the failure names the culprit repo; a sibling that had already cloned is reused, not re-cloned. Fix whatever the clone error names — usually credentials or a URL, see Git access to clone the repository in Connecting repositories — and the next retry picks it up.
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 it connects. No device registered yet? See How do I add a device?. Or set up a MicroVM runner so CodeHerder launches one on its own next time.
Agent not eligible on any linked device
Signal: The device is Online but not picking up work for this agent.
An agent is eligible on any device linked to its workspace (or an ancestor group) whose capabilities the device covers — no separate assignment step. Check what the engine sees:
ch task placement <taskId> --agent <agentId>
ch agent placement <agentId> # the agent alone, regardless of task
Either lists every candidate device and, for each ineligible one, the exact refusal reason. The report doesn’t check device health; see Unhealthy device and The Placement report.
Secret reference unacknowledged or unresolvable
Signal: The device is Online and the agent is eligible on it, 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 or a secret variable the device owner hasn’t acknowledged, or a secret reference that couldn’t be resolved on the device.
What happened: This is the same acknowledgement gate for two things. A launch config’s device-side credential (an @secret:<key> value) must be explicitly acknowledged by the device or a workspace owner, with the value actually stored there. A secret variable at group, workspace, device, or agent scope needs the same acknowledgement, even though it isn’t an @secret: reference. Either one missing refuses the session to start — including after adding a new key to an already-acknowledged config.
What to do: Set the device’s acknowledged-secrets list to include every referenced key, @secret: or secret-variable alike:
ch device secrets set <deviceId> --secret <key>
This is a whole-set replace, not a diff — pass every key in scope, not just the new one. If the failure says a reference couldn’t be resolved instead, it won’t name the key — check which @secret:<key> references the launch config uses and confirm each is stored on that device. See Secrets on a device and Variables.
Device at capacity
Signal: The device is Online and eligible agents exist for it, 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. At the limit, new tasks queue and start as a running session finishes; raise the limit in the same panel for more parallel work.
If the device stays full with nothing seeming to run, some worktree slots may be left over from ended sessions. Expand the row and check Worktree slots in the Health snapshot — if it reports reclaimable slots, force an immediate clean-up:
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 an amber Not staffable badge next to the health badge. If a session was attempted and failed, a task comment says the device is unhealthy and names the failing check.
What happened: CodeHerder runs readiness checks on every device and withholds new sessions from any device whose health rolls up to unhealthy — at least one blocking check failed. Common causes: expiring git credentials, an unwritable session-root directory, critically low disk space, or a coding CLI installed but not signed in (see Coding agent not signed in below).
What to do:
- In Set up → Devices, click the device row to expand it.
- The Health snapshot panel lists every failing check with a summary and remediation tip. If you only see a notice, ask the device’s owner or an admin to read it — see Who sees host details.
- Fix it on the device — for example, re-run
gh auth setup-git, or free disk space. - The device re-checks within roughly five minutes, or immediately on restarting
ch device-server. Once it passes, the Not staffable badge disappears.
For every readiness check, 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 an amber Not staffable badge next to the health badge. 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 tests each installed CLI, and blocks work that needs a CLI that is signed out. This is the most common cause of an unhealthy, non-staffable device right after you register it.
What to do:
- On the device, sign in with that harness’s own login command —
claude login,codex login,cursor-agent login(or setCURSOR_API_KEY), oropencode auth login, matching the CLI the agent’s config uses. Pi has no login command: it signs in via a provider API key (for exampleANTHROPIC_API_KEY) in the device’s environment, or a credential file already signed in elsewhere. See Choosing the coding-agent CLI your agents run.- If the failing harness is installed for reasons unrelated to CodeHerder, exclude it instead of signing in: restart
ch device-serverwith--harness-exclude <name>(orCH_DS_HARNESS_EXCLUDE=<name>). It then no longer counts against Harness ready or Harness auth — but keep at least one harness on the device, or Harness ready fails instead.
- If the failing harness is installed for reasons unrelated to CodeHerder, exclude it instead of signing in: restart
- The device re-checks automatically within roughly five minutes, or immediately if you restart
ch device-server. - Once the check passes, the Not staffable badge 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 STATE column, or ch device show <deviceId> prints drained: yes (since <when>).
What happened: Someone paused the device — Disable in the web app or ch device disable <deviceId> — to stop new work landing there without interrupting running sessions. Online state and health badge don’t change; only new placement is skipped.
What to do: Resume placement:
ch device enable <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.
Claude usage or spend limit exhausted
Signal: The device is Online, healthy, not at capacity, and has an eligible agent — yet tasks still do not start. In Set up → Devices, the device carries a Claude exhausted badge next to its health badge (or Claude subscription exhausted on its detail page). ch device show <deviceId> prints the same thing as a staffing row.
What happened: CodeHerder monitors each device’s Claude usage — a subscription plan’s usage pools, or an enterprise/organization credential’s spend caps. When a meter reaches 100% before it resets, the engine stops routing new tasks there to avoid stranding sessions on a credential that will reject them. Running sessions aren’t interrupted, and the device’s Online/healthy status doesn’t change — its health checks don’t reflect this.
What to do: No manual action is required. Once the reset time shown on the meter passes, the engine resumes placing tasks automatically. The resets in text shows how long to wait.
If the stage’s Assignee is Auto — best fit and a second device with headroom is registered, CodeHerder already tries it — no action needed. Otherwise, the fix is a second Online, healthy device with a capable agent, or simply waiting 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.
In a backoff window after a recent failure
Signal: The activity feed’s cause names a backoff window after a prior failure.
What happened: After a session fails to start, CodeHerder waits out a short backoff window instead of retrying in a tight loop.
What to do: Nothing — it retries automatically once the window ends. If it keeps re-entering backoff, check the task’s Sessions view for what the failed attempt reported and follow the matching cause on this page.
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 three distinct causes that produce this signal:
No agent’s launch config covers what the stage requires. Workflow stages declare capabilities a launch config must carry — for example, a planning stage may require model:opus. Set these with --cap in ch agent config edit, or --config-cap at agent-creation time (see Creating a runnable agent in one call). If no launch config in your workspace carries the needed capability, the stage is unstaffable.
The web app surfaces this as a Capability gap callout at the top of Set up → Agents, naming what’s missing:
ch agent config edit <agentId> --cap <existing-cap> --cap <missing-cap>
--cap replaces the whole capability list, so name every existing one plus the missing one. config edit leaves other fields — command, args, environment — as they are. For a brand-new agent, create it and its launch config together: ch agent create --harness <h> --config-cap <capability>. See Agents and the CLI.
The task’s own required capabilities are no longer covered. A task can carry its own --cap requirements, combined with each stage’s as the task moves through the workflow — see Capabilities. A task that passed the create-time check can still stall later once a stage pushes the combined set past what any launch config covers.
This shows a Start refused badge (task list and detail) and a Start refusal row naming the reason; ch task show <taskId> and --json’s startRefusal carry the same. Fix with ch task edit <taskId> --cap <label>... or --clear-caps.
The agent needs tooling the device lacks. An agent can require host tools — the AI coding binary or the git-host CLI (gh/glab) — which devices detect automatically. Install the missing tool; the device picks it up on its next connection. See Managing your devices.
Current stage doesn’t run an agent
Signal: The activity feed’s cause names the current stage as one that doesn’t run an agent.
What happened: A pipeline’s start and end stages, like todo and done, are bridge stages that never spawn an agent by design — see Staffing coverage. An epic or initiative reports this for its own container stage the whole time children run, which needs no action. Otherwise, on a story, feature, bug, or task build stage, it usually means that stage’s workflow has no real agent stage attached.
What to do: For a bridge or container stage, do nothing — it’s expected. Otherwise, the workflow’s stage list needs a real agent stage there; see How work flows.
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 it’s missing or its token expired, whichever stage runs first — code opening the request, or merge landing it — 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 loginon the device. - GitLab:
glab auth loginon 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. Auto-staffing is off at the entry stage
A task can sit at its workflow’s very first stage, untouched, because auto-staffing is off for that task. One setting controls it, auto_staff_unassigned. Start by reading what applies to this task:
ch workflow show <taskId>
That prints the workflow this task actually runs on. Look for auto_staff_unassigned:
true— auto-staffing is on for this task. Check the other causes on this page instead.- Absent, or
null— auto-staffing is off (a missing key andnullmean the same thing). Keep reading.
When it’s off, the setting comes from the task’s own workflow override if it has one, or its type’s shared settings otherwise. Read the type’s shared settings next — --json is required, since the plain-text output omits this setting, and it lists every type, so select <type>’s entry yourself (--type <type> narrows plain text only, not --json):
ch workspace workflow show --json | jq -r '.data[] | select(.typeKey == "<type>") | .schema.auto_staff_unassigned'
Without jq, find <type>’s entry in ch workspace workflow show --json by hand and read auto_staff_unassigned inside its schema object — don’t read the first one you see, it may belong to a different type.
Compare the two readings:
- Both are absent or
null— the type itself doesn’t auto-staff. See Auto-staffing is off for the whole type, below. - The type reads
true, but the task reads absent ornull— this task carries its own workflow override. See A task held back by its own workflow override, below.
Auto-staffing is off for the whole type
Signal: Both readings above are absent or null.
What happened: This type’s entry stage leaves newly created, unassigned tasks alone, often deliberately, so a human triages or assigns each one first. On the built-in task type it can instead mean this workspace holds an out-of-date copy — each workspace gets its own copy of a type at creation, and one created before a later fix keeps the old setting until a server upgrade repairs it. Only suspect that when nobody turned the setting off on purpose.
What to do: Move the task on by hand:
ch task status <taskId> <stage>
To change it for future tasks of this type, a workspace admin or owner uploads a corrected schema with auto_staff_unassigned set to true:
ch workspace workflow edit <type> --from-file schema.json
If the type is the built-in task and nobody turned the setting off, upgrade the server instead. The upgrade repairs every stored task row automatically. See Assigning and claiming work.
A task held back by its own workflow override
Signal: The type reads true, but the task reads absent or null.
What happened: A task can carry its own workflow override — a private copy that keeps the auto-staffing setting as it stood when made, and doesn’t follow later changes to the type. An override made while the type was off keeps this task off even after someone turns the type back on.
What to do: Move the task on by hand with ch task status <taskId> <stage>, or clear the override so the task follows its type again:
ch workflow override clear <taskId>
Clearing it reverts the task to the type’s current settings. Workspace admins and owners can run it. If the task sits at a stage the type’s workflow doesn’t have, the command stops and tells you to re-run it with --migrate <oldStage>=<newStage>. See Assigning and claiming work.
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. The advance to <stage> row in Start with the advance row above names the open child count too, once the container is otherwise ready to close.
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: This task was left at in_progress before CodeHerder started refusing that move for these four types. in_progress is the correct holding stage for an initiative or epic, but it was never a stage in the story, feature, bug, or task pipeline, so nothing gets staffed there.
What to do: Reopen it straight into the stage you actually want:
ch task reopen <taskId> code --reason "picking this back up"
Or, from the web app, open the task and use the reopen control under its More menu. See Reopening a cancelled or completed task in Editing and cancelling tasks for the full target set.
9. Refused by a change-shape limit
Signal: Advancing the task fails with an error naming a counter and a threshold, for example advancing out of stage "code" is blocked by its change-shape gate: max_files_changed is 34, exceeding the threshold of 20.
What happened: The stage you’re leaving has an optional limit on how big or spread out the task’s change can be, and the task’s recorded change shape exceeds it. This only happens on a stage where someone has deliberately set such a limit — most workspaces never see this message at all.
What to do: Either split the change into smaller tasks so each one fits under the limit, or — if the limit is too tight for this kind of work — raise or remove it on that stage. See Change shape for what the counters measure and how to adjust the limit. The advance to <stage> row in Start with the advance row above shows this same refusal before you even attempt the move.
10. A required field is missing at the current stage
Signal: Advancing the task fails with an error listing one or more field keys as still needed, the task page shows a note naming which required fields are missing instead of an Advance to <stage> button, or ch task show <taskId>’s advance to <stage> row names the field.
What happened: Every stage has a gate — a check that its required fields are filled — and one is still empty. On the built-in story/feature workflow this most often means the task is at plan: it can’t leave until both acceptance and plan are set, from different sources. You write acceptance when filing the task — see Write acceptance criteria — and the planning agent reads it, then writes plan in return.
What to do: Run ch task fields <taskId> to see exactly what this task’s type requires and which fields are still unset:
ch task fields <taskId>
If acceptance is what’s missing, you need to write it yourself — no agent will:
ch task edit <taskId> --acceptance-file criteria.md
If plan is what’s missing and the planning agent is still working, give the session time to finish — it doesn’t appear until the agent saves it. If the field is genuinely stuck empty, fill it yourself:
ch task edit <taskId> --plan-file plan.md
See Planning stages and stage gates in How work flows for the full gate table, and Writing tasks an agent can build for what each field holds.
Related guides
- When a session goes quiet — recovers on its own, not a cause of a stuck task
- The Placement report — the full picture of device eligibility
- Blockers and blocked tasks — the full blocker lifecycle
- Tasks that span several repositories — the repo set model
- How work flows — the stages every task moves through
- Writing tasks an agent can build — how to file work agents can pick up
- Assigning and claiming work — who ends up working a task
- Approvals & staying in control — gate advances behind a designated approver
- Reviewing an agent’s work — the human side of the review stage
- How do I add a device? — connect a machine if none is registered yet
- Secrets on a device — device-side credentials and their acknowledgement
- Variables — environment variables and secrets at every scope
- Change shape — the size/spread limit that can refuse a stage advance
- Reading the task flow report — the same wait states and refusals, aggregated across the whole workspace
Last updated