Stalled or stuck feature

Diagnose and clear a feature that is dispatched but not progressing: no-task-progress stalls, provider-preparation exhaustion, build-progress ceilings, rate-limit waits, auth parks, and kickback loops. For operators reading .pipeline/ state and the daemon log.

Not sure this is the right runbook? Run /daemon-triage — it gathers the evidence read-only, classifies the failure, and routes to the one runbook that owns it.

Symptom

Any of these:

  • The daemon log shows ▶ start <slug> and then nothing for a long time.
  • ■ done <slug>: halted or ■ done <slug>: error appears and the slug stops being dispatched.
  • The startup dashboard lists the slug under HALTED instead of IN-PROGRESS or ELIGIBLE.
  • The dashboard lists a reason and remedy: line for a PARKED, HALTED, or retained slug.
  • The same build step retries repeatedly with the same reason.
  • A feature is marked complete but the PR never appeared.

Diagnosis

All per-feature evidence lives in the feature’s own worktree: .worktrees/<slug>/.pipeline/. All daemon-level evidence lives at the main repo root: .daemon/.

Blocked merged spec

Symptom: A merged plan never dispatches, and ai-conductor daemon status lists it in BLOCKED with a reason and remedy. This is a discovery refusal, not a feature HALT: no feature worktree or .pipeline/HALT exists yet. The startup dashboard does not render this state (#1332); use daemon status.

Diagnosis: Read the BLOCKED line. Common reasons are unresolvable-stories-ref, stories-missing, stories-not-approved, adr-not-approved, no-dependency-tree, and missing-coherence. adr-not-approved means the default branch’s .docs/decisions/ corpus contains an ADR whose first declared status is not APPROVED or SUPERSEDED (or that declares no status); it blocks every merged spec, not just the one it names. The snapshot is .daemon/blocked.json; a missing or malformed snapshot means only that no completed discovery pass is available, not that the spec is clear.

Recovery: Apply the line’s remedy on the repository’s default branch. Do not repair only a feature branch or a local working tree: discovery reads the committed default-branch tree. After an upgrade that adds this visibility, the first discovery pass may dispatch a previously invisible, otherwise-buildable merged spec when the repository has no processed marker for it. That is expected; review the plan before starting the daemon if those older specs are not ready to build.

Verification: Run ai-conductor daemon status after the next pass. A repaired spec disappears from BLOCKED and is eligible for dispatch; a remaining entry includes its current remedy.

First, read the committed halt record

For an operator-actionable halt, read .docs/halted/<slug>.md from the feature branch before consulting worktree-local state. The committed record survives a recreated worktree and can be read from any clone that has the feature branch:

git show <feature-branch>:.docs/halted/<slug>.md

It gives the halt class, step, phase, branch, HEAD SHA, timestamp, and complete HALT body. A Status: resolved record preserves those original details and also records how and when it was cleared. mechanical halts deliberately create no record, so begin with the live marker when this file is absent for that class.

The push is best-effort. If the daemon reports halt_record_push_failed, the record was still committed locally on the daemon host but may not be visible from the remote branch. Read it from that feature worktree instead:

git -C .worktrees/<slug> show HEAD:.docs/halted/<slug>.md

Repair the push failure separately; do not treat a missing remote copy as a missing halt record.

1. Read the halt marker

head -1 .worktrees/<slug>/.pipeline/HALT
cat .worktrees/<slug>/.pipeline/HALT.class

.pipeline/HALT is the durable park state for that feature: the daemon never advances, opens a PR, or merges past it. The first non-empty line of the body is the reason the dashboard surfaces. Read it by name during recovery; persisted halt events in .pipeline/events.jsonl supplement this marker and do not replace it.

.pipeline/HALT.class classifies it. Every HALT the daemon writes now carries one — the daemon stamps any HALT still missing a class at startup as legacy, once per feature, so the sidecar is never absent for long. Content the reader doesn’t recognize still reads as unclassified.

Class Meaning Cleared by the re-kick sweep?
needs-human Only an operator can resolve it. A detail naming a remediation-planner mismatch is a plan gap wearing this class; see the remediation planner disagreed with the parsed findings. No — except a matching, one-use kickback-budget authorization for a budget-cap halt.
kickback-cap A bounded prd_audit or as-built-review remediation allowance is exhausted. No — except a matching, one-use kickback-budget authorization.
plan-gap prd_audit or the as-built review found an outcome no active plan task owns; see the plan-gap recovery. No — skipped on every sweep.
mechanical The daemon may safely retry it. Yes, on a base-branch advance.
protected-artifact BUILD or SHIP found a genuine protected DECIDE-artifact violation. No — skipped on every sweep until an operator resolves it.
legacy Predates total classification; stamped by the daemon’s startup migration. Yes, on a base-branch advance, same as mechanical.
(absent / unrecognized) Treated as unclassified. No — skipped on every sweep, same as needs-human.

A feature that errors gets a diagnostic HALT written at the deterministic .worktrees/<slug>/.pipeline/HALT path even when worktree creation itself fails. Read and clear that marker after fixing the cause. If the daemon log reports unrecoverable-state, it could not write that marker; repair the target directory or permissions first, then re-run the dispatch.

NEVER-STARTED is not a halt: the dashboard found no readable conduct-state.json, and the feature remains dispatchable. Do not reclaim or unpark it merely to make it run. For a retained row, follow its remedy: line: an open PR needs no action until it lands; a closed, unknown, or legacy PR state can be handled with ai-conductor daemon reclaim-worktree <slug> when reclaim is appropriate.

2. Classify the stall

grep -E 'build_stall|build_no_progress|build_active_stall|zero_work_product|rate_limit|credentials_park' \
  .worktrees/<slug>/.pipeline/events.jsonl | tail -20

A later step is blocked by an unsatisfied prerequisite

Symptom: .pipeline/HALT says Step '<step>' is blocked by unsatisfied prerequisite and lists one or more prerequisite names with their recorded statuses. The HALT class is needs-human.

Diagnosis: The listed prerequisite cannot run again automatically: it is no longer pending. Read that prerequisite’s halt record or its earlier events before changing anything. A refused status means the prerequisite’s own work was not judged failed; its original HALT explains the operator action required.

Recovery: Resolve the prerequisite’s stated cause, then clear HALT and HALT.class using the resume procedure. Do not hand-edit conduct-state.json to mark the prerequisite done or failed. The resumed conductor admits the unsatisfied prerequisite again and preserves already completed steps.

Verification: Run ai-conductor daemon status after the next dispatch. The prerequisite runs again; the blocked later step does not remain the terminal step unless the underlying condition is still unresolved.

no_task_progress

The build step’s circuit breaker. From attempt 2 onward, the step is declared stalled only when both signals stay pinned across the attempt: the count of resolved plan tasks did not increase, and HEAD did not move. The resolved count is the union of two sources:

  • rows in .pipeline/task-status.json whose status is completed or skipped, and
  • plan task ids carried by Task: <id> trailers on commits in merge-base(origin/<default-branch>, HEAD)..HEAD.

So a task committed with a valid Task: trailer counts as resolved even if its row was never flipped. Work committed with no Task: trailer and no row update is invisible to the count — but not to the breaker, because it still moves HEAD.

Commit movement is the liveness authority (adr-2026-07-23-commit-movement-liveness-floor). The attributed-task count is advisory routing and telemetry; it never independently proves a build is stuck. An attempt that lands real, committed-but-un-trailered work moves HEAD while the count stays pinned, and is classified unattributed_progress — telemetry, not a stall — instead of no_task_progress. A SHA read that fails on either side of the attempt is treated fail-closed, degrading to the old count-only behavior. A genuine wedge (zero commits, count pinned) still stalls and halts exactly as before: count alone can never kill a build.

If the build step’s retry budget then exhausts with at least one HEAD-moving attempt, the run does not take the generic “retries exhausted” HALT. It routes into build_review — the sole completion authority — through the same seam a normally-completed build uses, recording the unresolved plan task ids in conduct-state.json (build_routed_reason). This route requires a clean worktree. That is not an always-pass: build_review re-grades the diff independently and can still FAIL the routed build, kicking it back to build under the same MAX_KICKBACKS_PER_GATE bound as any other build_review kickback. A build with zero commit movement across every attempt never routes.

active_stall

Symptom: the daemon log shows <step> active without movement for <N>m (<resolved>/<total>) · action <warn|end_attempt>, and events.jsonl has a build_active_stall event. With end_attempt, a build_stall event with reason active_stall follows.

Diagnosis: the provider is still writing heartbeats, but no task resolved and HEAD did not move for build_progress.active_stall_minutes. warn only reports it. end_attempt ended the attempt; that spends one ordinary retry, and a pinned retry from attempt 2 onward becomes no_task_progress.

Recovery: inspect the attempt’s transcript for a loop or an unreachable task. To end such attempts automatically, set build_progress.active_stall_action: end_attempt; to tolerate long non-committing work, raise active_stall_minutes. See build_progress.

Verification: later build_progress events show activity: active-committing, and the log stops repeating the active-stall warning.

Build halted with uncommitted paths

Symptom: .pipeline/HALT names one or more uncommitted paths, for example 2 uncommitted paths: src/a.ts, test/a.test.ts. The build cannot complete or use the exhausted- but-working route while those paths remain dirty.

Recovery: inspect the named paths, then either commit the intended work or discard only the named paths when it is not intended. Do not clear the halt first or re-dispatch around the dirty tree. Once the worktree is clean, use the resume procedure.

Verification: git status --porcelain in the feature worktree prints no output before the resumed build advances.

In daemon mode the engine synthesizes a remediation prompt into .pipeline/build-stall-question.md and dispatches /remediate, bounded to 2 remediation rounds per gate. A zero-work stall never terminal-HALTs from that path; it falls through to the ordinary retry and park route.

Unresolved step command

Symptom: .pipeline/HALT reads Cannot dispatch '<step>': /<command> is not available in the provider skill catalog. and its class is mechanical.

Diagnosis: the provider ran but reported the exact dispatched slash command as unknown — the run’s home is missing the skill that step renders to. This is a deterministic environment failure: retrying, escalating model or effort, or walking providers cannot make a command appear in a catalog that does not have it, so the conductor halts immediately instead of spending further attempts. Under Pi, the engine raises the same HALT before spawning pi when <name>/SKILL.md exists in none of ~/.agents/skills, $PI_CODING_AGENT_DIR/skills (default ~/.pi/agent/skills), or the project’s .agents/skills.

Recovery: re-provision the provider home (or self-host sandbox) with the missing skill, then clear the HALT using the resume procedure. Because this is a mechanical-class HALT, the daemon’s ordinary re-kick sweep also clears it on a base-branch advance — but re-kicking without fixing the catalog just reproduces the halt on the next dispatch.

Verification: after the next dispatch, the daemon log shows the step’s command resolving and the run advancing past it; it must not return to the same commandUnresolved HALT.

Unresolved build-review rubric skill

Symptom: the event log contains build_review_rubric_infrastructure_failure with reason invalid-provider-result. Its excerpt says that a build-review rubric skill could not be dispatched and names the unresolved command when the provider supplied it.

Diagnosis: the rubric produced no judgement. The provider reported that its dispatched skill command is unavailable, so the conductor stops that auxiliary member immediately instead of retrying it, requesting a judged-result repair, or trying another configured provider.

Recovery: relink or re-provision the provider skill catalog. If the feature branch predates the rubric skill, rebase it onto a base that contains the skill. Then use the resume procedure when the feature has a HALT; otherwise rerun the blocked build-review gate.

Verification: the next build-review event is a rubric result or another actionable infrastructure failure, not the same unresolved-command excerpt.

Provider preparation exhausted

Symptom: .pipeline/HALT begins Provider preparation exhausted. and its class is needs-human. The daemon log or dashboard may also show the provider lifecycle as recovering followed by halted with preparation-timeout-exhausted and recovery 1.

Diagnosis: read the marker body:

cat .worktrees/<slug>/.pipeline/HALT
cat .worktrees/<slug>/.pipeline/HALT.class

It names the logical step, phase: preparing, attempt id, elapsed milliseconds, and recovery count. Self-host dispatches waiting for root-mutation admission are queued outside this deadline; the daemon log and event ledger distinguish queued, admitted, and cancelled (operator park). Queue time consumes no preparation recovery attempt.

After admission, the timeout applies before a provider process starts. It means candidate resolution, session setup, or self-host preparation did not complete within provider_preparation_timeout_minutes. The first expiration already used the one automatic replacement; because this is a needs-human HALT, the re-kick sweep will not clear it.

Do not diagnose this from .pipeline/step-heartbeat. Heartbeats are activity telemetry only, and a quiet running provider is not automatically killed, retried, or replaced.

Recovery: repair the preparation failure. If preparation is expected to take longer, increase provider_preparation_timeout_minutes in the active configuration; 0 or a negative value disables that pre-spawn deadline. Do not use step_heartbeat_stall_minutes: it is a deprecated compatibility no-op and cannot change provider lifecycle behavior. Then clear the HALT using the resume procedure.

Verification: after the next dispatch, the dashboard/log shows a new preparing attempt and, once spawned, running; it must not return to halted with preparation-timeout-exhausted. Confirm the resumed feature with the general verification steps.

Live-boundary violation (self-host only)

If .pipeline/HALT reads <live checkout|provider state> changed during self-host execution — N added, N removed, N changed: …, the self-host live boundary detected a change to the harness checkout or to the operator’s real ~/.claude/~/.codex while a step was in flight. The reason names the paths — each tagged added, removed, or changed, capped at eight entries followed by and N more, with exact counts. Read them before investigating anything else.

First determine whether the completed dispatch was proven contained. A contained dispatch records self_host_containment_verdict and, when the live checkout changed, a contained_live_checkout_drift event in .pipeline/events.jsonl. That event names the containment evidence, labels the drift concurrent-operator, and does not write a HALT: the dispatch could not have made the live checkout writable. A clean contained dispatch still records its containment verdict. If the verdict says containment was unavailable, disabled, or its probe failed, the ordinary fail-closed behavior below applies.

  • A path under the live checkout is usually an untracked file an operator session wrote — a permission or approval grant (Claude Code’s .claude/settings.local.json is untracked and fingerprinted), a scratch or generated artifact, or a new file staged with git add. Git can say a path is tracked but not who wrote it, so the guard fails closed on everything it cannot attribute. An edit or deletion of an already-tracked file reports M/D and does not halt; a git operation that rewrites tracked content without leaving it modified (git pull, git checkout, git stash) does halt, because git status then comes back clean. Batch root-checkout work between dispatches, or do it inside a worktree — see the live-checkout rule in AGENT_INSTRUCTIONS.md’s Daemon Operations Safety section. Issue #1301 tracks the attribution machinery that will remove this false-halt class.
  • A provider-state path that is config-like (settings.json, config.toml, hooks.json) means an unrelated interactive session changed operator config mid-build. That trip is deliberate and fail-closed — those files stay fingerprinted precisely because a real leak would look identical.
  • A provider-state path that is pure telemetry or cache means the exclusion list needs a new entry; see self-hosting: provider-state exclusions.

EROFS during a contained dispatch: a Read-only file system failure usually means the dispatch attempted a legitimate live-checkout write that is absent from the shared LIVE_CHECKOUT_VOLATILE policy. Confirm the attempted path from the provider output and verify it is an intended, safe volatile surface. As a short-lived recovery, set harness_self_host.live_containment: false for the affected run; this restores the fail-closed boundary guard, so avoid concurrent root-checkout changes. Then correct the shared LIVE_CHECKOUT_VOLATILE policy and its containment bind set with focused tests; do not add an ad-hoc local carve-out. Resume the feature using the resume procedure.

Verification: after resume, inspect .pipeline/events.jsonl. A repaired contained dispatch has one self_host_containment_verdict with contained: true; concurrent live-checkout drift, if any, has one contained_live_checkout_drift record with attribution: "concurrent-operator" and its changed-path summary. A temporary opt-out instead must either remain clean or halt with the uncontained reason, never silently ignore drift.

This is a mechanical-class HALT, so the daemon’s ordinary re-kick sweep clears it on the next base-branch advance. The step that was running keeps its own real verdict, so the re-kick resumes after it rather than repeating it.

Retained draft PR identity is unavailable (self-host only)

Symptom: .pipeline/HALT begins Self-host release gate HALT: retained draft PR identity is unavailable — no OPEN pull request exists for <branch> into <base>.

Diagnosis: the release gate reads its release metadata from the draft PR opened at SHIP-phase entry. When the in-memory URL is absent — the ship-start git push failed, for instance — the engine looks the PR up by branch instead, and only an OPEN PR whose head is that branch and whose base is the build’s base branch satisfies it. Reproduce the same lookup:

gh pr list --head <branch> --base <base> --state open --json url,state
  • Rows come back. The gate should have resolved it; capture the daemon log line [ship-draft-pr] branch lookup for <branch> … and treat it as a bug.
  • Empty, but a PR exists in --state all. It is closed or merged. That is not a draft the finish step may flip or rewrite — open a fresh PR for the branch, or land the work as already-shipped via ai-conductor shipped-record.
  • Empty entirely. No PR was ever opened; check whether the branch reached origin (git push failures are logged as [ship-draft-pr] push of <branch> failed) and push it.

Recovery: ensure an OPEN PR exists for the branch into the base with valid release metadata in its body, then clear the HALT using the resume procedure.

FINISH publication halts

A FINISH publication failure or non-converging progress halts one of four ways, and the marker says which:

  • FINISH publication retry exhausted: <reason> — the reason was transient (transport, GitHub, filesystem, provider judgment, or a re-observation), so every attempt in the budget was spent before giving up.
  • FINISH publication cannot proceed: <reason> is not retryable — … — the reason can never be satisfied by re-running the identical transition, so the run halted on the first observation and the retry budget was deliberately left unspent. Only author_pr_prose and judge_pr_prose cross the provider boundary between attempts, so no retry authors a commit, wires a missing effect, or reconciles a remote. These are draft_pr_no-commits, draft_pr_skipped, draft_pr_lease-rejected, and the five *_effect_unavailable reasons. Both halts are needs-human; recovery is the same — resolve the cited condition, then clear the HALT.
  • FINISH publication progress allowance exhausted after <N> transition(s); last transition: <transition>. Human review required. — FINISH made verified publication progress, so none of the step retry budget was spent. The separate progress allowance reached its 14-transition bound (two passes over each of the seven publication transitions) before the publication state converged, and this is a needs-human halt. When the last prose transition follows a judged-deficient revision, the halt also ends with Detail: <judge objection>.
  • A plain-prose sentence with no FINISH publication … prefix, e.g. The PR prose judgment was refused and requires an operator decision. Next action: Review the refusal and decide how to continue publication. and, when the provider supplied one, a trailing Detail: <provider text>. This is a human_required disposition — a condition only an operator can resolve, distinct from the three retryable/non-retryable shapes above. Its reasons cover judgment refusal or halt prose (judgment_refused, judgment_halt_prose), a PR that still carries a needs-remediation title prefix, banner, label, or body marker (halt_state_pr — resolved before judgment is ever dispatched, so no provider pass is spent), a publication transition that ran but left its owned state unchanged (publication_transition_unmoved — the detail names the transition and the state that did not move, whether the transition reported success or asked for a retry it could not perform), an unresolvable PR match or shipped record (ambiguous_pr_identity, invalid_shipped_record), and destructive or unrecognized publication intent (interactive_intent_*, unattended_intent_*). It is always a needs-human halt.

Diagnosis: inspect the named last transition and the preceding FINISH publication events in the daemon log. Fourteen verified transitions without convergence means the publication state machine is cycling or an external publication state is not settling; do not clear the HALT merely to repeat the same cycle. An author_pr_prose / judge_pr_prose alternation means the authoring pass keeps producing prose the judgment pass rejects. The Detail: text on a prose-related halt is the judgment for the exact rejected revision; use it with the current PR body to correct the prose before resuming.

Recovery: reconcile the cited transition and the external PR/remote state until the next FINISH entry can converge. Only then clear the HALT using the resume procedure.

An unrecognised reason always keeps its retries: the classifier fails closed toward retrying.

Clearing any FINISH publication halt is enough to get the feature back: see a cleared FINISH halt resumes to a recorded ship for what the daemon does next and how to confirm it. Never repair a halted FINISH by hand — an operator-opened PR is not a harness finish, and the daemon has no way to learn about it.

The post-FINISH shipment audit refused the ship

Symptom: the daemon logs ✋ <slug> false-ship halted — worktree kept (durable shipment evidence refused ship: <code> (expected <sha>, observed <sha>)), and .pipeline/HALT plus .docs/halted/<slug>.md carry the same sentence. The audit runs after FINISH converges: it re-proves the durable .docs/shipped/<slug>.md record against the commit the implementation PR will merge, and refuses every terminal ship side effect when that proof fails.

Diagnosis: the parenthesised pair is the whole diagnosis. expected is the head the PR reported (gh pr view <url> --json headRefOid); observed is the commit the audit validated. The same pair is on the event spine as shipment_evidence_refused and in .pipeline/audit-trail/events.jsonl, so it stays readable after the branch moves on. For shipment-candidate-not-on-implementation-head the two commits are unreachable from each other, or the head’s own tree carries no valid record — most often a record that was committed but never pushed. Confirm with:

git -C .worktrees/<slug> show <expected-sha>:.docs/shipped/<slug>.md

A local branch that is merely ahead of the reported head is not a refusal on its own: the conductor’s post-finish cost refresh commits and pushes after publication, and GitHub updates a pull request’s head asynchronously, so the audit accepts that shape whenever the head it names carries the record itself.

Recovery: push the record-bearing commit to the PR branch, then clear the HALT using the resume procedure. Never open or repair the PR by hand — see the manual-PR warning.

draft_pr_lease-rejected

Symptom: .pipeline/HALT reads FINISH publication cannot proceed: draft_pr_lease-rejected is not retryable — the remote branch carries commits this checkout has never observed ….

Diagnosis: establish_pr publishes the feature branch with git push -u origin <branch> --force-with-lease, because the finish-time rebase step has just rewritten that branch’s history — the branch diverges from its own remote by construction, and a plain push could never succeed. A rejected lease means something else: the remote branch carries commits this worktree has never observed, so the push was refused rather than overwriting them. (An ordinary transport or permission failure reports draft_pr_push-failed instead.)

git -C .worktrees/<slug> fetch origin <branch>
git -C .worktrees/<slug> log --oneline HEAD..origin/<branch>
  • Rows come back. Real unseen work is on the remote. Integrate it (rebase the feature branch onto origin/<branch>, or reconcile by hand) before letting FINISH retry.
  • Empty. The remote-tracking ref was merely stale; the fetch above refreshes the lease.

Recovery: once HEAD..origin/<branch> is empty, clear the HALT using the resume procedure. Never resolve this with a bare git push --force — that discards the very commits the lease protected.

halt_marker

The pipeline skill wrote .pipeline/halt-user-input-required — a genuine question that no retry will answer. Its content becomes the stall question:

cat .worktrees/<slug>/.pipeline/halt-user-input-required
cat .worktrees/<slug>/.pipeline/build-stall-question.md

The build completion gate returns “not done” while that marker exists, so a surviving marker also blocks the build gate directly.

ENVIRONMENT_CLAIM_REFUTED

A step failed with a reason beginning ENVIRONMENT_CLAIM_REFUTED. That is not an environment problem: the dispatch made an operation-specific claim that the environment blocked git push or gh, and the engine disproved it from the dispatch it actually performed (unsandboxed claude, and a write fence whose generated script carries no such rule). The failure message quotes the claim and states the facts.

Nothing needs fixing in the sandbox — do not go looking for one. The attempt is retried with the disproof as its retry hint so the step performs the operation for real. If the same refutation repeats until the budget is exhausted, the model is inventing the blocker rather than running the command: escalate the step’s model tier or take the finish over manually (a manual PR still needs its shipped-record, see shipped-record reconciliation).

Claims from codex are never refuted — its unattended runs really are sandboxed (sandbox_mode="workspace-write"), so a blocked operation there may be genuine.

A claim that the environment blocks all, every, or any Bash, shell, tool, or terminal command (or blocks everything) also does not produce this marker. The audit passes that broader claim through unchanged rather than selectively refuting a named operation in the same output.

Build-progress ceilings

A build that is resolving new tasks re-dispatches without consuming the fixed retry budget, bounded by the build_progress_halt block. Defaults: enabled, attempt_ceiling: 30, dispatch_ceiling: 20. Hitting the attempt ceiling parks with a distinct reason so you can tell “genuinely stuck” apart from “still progressing but out of runway”. Key details are in configuration.

A refunded build retry log line includes a separate allowance fragment, for example progress allowance: attempt 2 of 30. The fixed retry counter continues to describe the reused fixed-budget slot and stays within its own maximum; the allowance fragment tells you how much of the independent progress-attempt ceiling the running build has consumed.

A halt awaiting operator action is neither progress-re-kicked nor cleared by rate-limit episode recovery; the daemon log names the blocking halt disposition.

The ▶ build <resolved>/<total> line counts a task as resolved when its .pipeline/task-status.json row reads completed/skipped or a commit on the branch carries its Task: <id> trailer — the same union the build completion gate routes on. Nothing writes those rows back mid-build except the pipeline skill’s explicit ai-conductor task done, so on a run where the agent only stamps trailers the rows stay pending and the trailers are what moves the number. A resolved count that does not advance while commitCount keeps ticking therefore means work is landing without task attribution — check the commit trailers before treating it as a stall.

Rate limits

A rate-limited dispatch emits rate_limit and waits — to the deadline parsed from the provider message when one is available, otherwise 300 seconds. The wait does not burn the retry budget. HALTs written while a rate-limit episode is active are stamped so they can be recovered when the episode ends. A halt awaiting operator action is neither progress-re-kicked nor cleared by episode recovery; the daemon log names the blocking halt disposition.

Known limitation. The episode stamp is in-memory, scoped to the running daemon process. Restart the daemon during an episode and its episode-caused halts are no longer recognized as recoverable — they must be cleared by hand or by a base-branch advance. Tracked in #1023.

Auth parks

credentials_park means the run is waiting on a credential source, not stuck on your code. credentials_park_progress events report readiness probes as they happen. The park times out after harness_self_host.auth_park_timeout_minutes (default 60), then HALTs with an actionable reason naming the credential to refresh.

ai-conductor build-auth-status

Exit 0 means clean (state=api-key when there is no daemon-owned token, or state=valid). Exit 1 covers missing, unreadable, invalid, and unverifiable, and prints the remediation message with the token path.

Kickback loops

A gate that blocks routes work back to an earlier step. Each gate allows at most 2 kickbacks; past that the run halts instead of looping. Separately, a kickback into build that ends with zero net progress and an unchanged gate verdict escalates to a halt immediately rather than spending the remaining budget. Read the verdict that keeps blocking:

cat .worktrees/<slug>/.pipeline/gates/<step>.json

The record is { satisfied, reason, checkedAt, kickback }. reason is the exact string the gate computed. The concepts behind gate verdicts are in gates; the per-step evidence files are listed in artifacts.

For a budget-cap halt, do not remove HALT or HALT.class. From the main checkout, inspect the budget and authorize the intended recovery:

ai-conductor kickback-budget inspect --feature <slug>
ai-conductor kickback-budget raise --feature <slug> --gate <gate> --by <positive-integer> --rationale "<why more allowance is justified>"
# or
ai-conductor kickback-budget reset --feature <slug> --gate <gate> --rationale "<why this count may restart>"

When the budget view identifies exhausted plan growth, recover it with raise: it extends the growth allowance. reset is for lap evidence only and cannot recover a plan-growth halt.

A prd_audit or as-built kickback-cap halt fires at the build transition, after the repair’s tasks are already appended and pending. Once the authorization is consumed, the feature resumes into build on those tasks without re-running the audit. If it halts kickback-cap again with a new generation, the raise was too small or named the other gate: re-inspect and raise the exhausted allowance.

The daemon clears only a live halt whose gate and generation match that one-use authorization. It does so on its next loop iteration, without waiting for a base-branch advance. If the feature was already operator-parked, unpark it after the command; otherwise it remains intentionally halted.

If the halt persists and inspect reports Resume authorization: stale, the authorization was bound to an earlier halt generation; the daemon log records it as stale and not consumed. Run raise or reset again against the live halt — the new authorization replaces the stale one. The full command contract is in the CLI reference.

BUILD verification after a repair

Symptom: a repair returned to BUILD and you need to determine whether test_suite reused evidence or derived it again.

Diagnosis: read the feature narrative, not merely the old gate files:

ai-conductor daemon logs | grep 'BUILD member .* settled:'

The daemon writes BUILD member test_suite settled: reuse (<basis>) or ... recompute (<basis>). reuse (fingerprint-match) reports still-valid full-suite evidence. A recompute line reports a closed reason such as fingerprint-mismatch or fresh-evidence-required. A prior passing suite result on disk is not a reason to skip the verifier; its current result decides satisfaction.

Recovery: do not create an operator park for the historical terminal-less stale-verdict path; it is retired. The common stale-proof case re-dispatches test_suite automatically, so let the re-verification round settle. If build_review instead HALTs needs-human because its inputs cannot change, rewind from the feature worktree rather than editing pipeline state:

cd .worktrees/<slug>
ai-conductor rewind --to test_suite

rewind clears the affected gate verdicts and both halt markers only after it has atomically demoted the feature to test_suite. It refuses a target that is not earlier than the recorded step. Do not hand-edit conduct-state.json, gate files, HALT, or HALT.class.

Verification: the log contains one test_suite settle line, followed by build_review or an explicit HALT. After a rewind, confirm it also prints Rewound to test_suite. and that the next dispatch starts at test_suite.

Build review cannot resolve the feature plan

Symptom: .pipeline/HALT begins build_review cannot resolve a plan for feature and lists candidate plan stems. Its class is needs-human; no provider was dispatched and no .pipeline/build-review.json verdict was written.

Diagnosis: More than one plan exists, but neither a recorded active plan nor a plan whose stem equals the feature slug identifies this feature’s plan. Read the named candidates; do not select one because of its alphabetical position.

Recovery: Restore or correct the feature’s authoritative plan through the normal DECIDE process. When several plan files remain, its filename stem must equal the feature slug. Then rerun the preceding verifier from the feature worktree:

cd .worktrees/<slug>
ai-conductor rewind --to test_suite

rewind clears the halt and stale gate state atomically, then runs test_suite before a fresh build_review. Do not delete the halt markers by hand or rename an unrelated plan to force a match.

Verification: The log shows Rewound to test_suite., then a test_suite settle line followed by build_review; the new review either has the resolved feature plan as input or reports an independent gate result.

Full-suite verification lock remains occupied

Symptom: test_suite reports Unable to acquire full-suite verification lock within 30000ms.

Diagnosis: the verifier serializes suite execution with .worktrees/<slug>/.pipeline/test-suite.lock. A dead lock owner is recovered automatically. If that recovery was interrupted, its recovery.json claim is also reclaimed when its recorded process is dead, or when unparseable claim bytes are at least five minutes old. A live claim and a fresh unparseable claim remain exclusive.

Recovery: rerun the verification or let the daemon’s next BUILD attempt run it. Do not delete the lock directory or its claim: a live verifier may own it. If verification instead reports an error naming a recovery claim, preserve the directory and diagnose the filesystem or process-probe failure before retrying.

Verification: the next test_suite run either settles normally or reports its actual suite failure; it does not remain blocked by a dead owner or reclaimable stale claim.

Setup failures

If the project’s bin/setup failed inside the worktree, the feature may be quarantined: .pipeline/QUARANTINE exists and a wip/setup-quarantine-<slug> branch holds the evidence.

Automatic setup-triage park

Symptom: a setup failure’s triage ends with a retained worktree and a main-root marker at .daemon/parked/<slug> whose first line begins auto-parked:. This is a durable automatic park, not a missing dispatch or a transient in-memory daemon state.

Diagnosis: read the first line of .worktrees/<slug>/.pipeline/HALT together with the marker. The first line has one of three meanings:

  • feature parked — will not re-dispatch on the next scan — the automatic marker was written (or was already present), so the daemon will keep the feature excluded.
  • feature errored — automatic park failed: …; run ai-conductor daemon park <slug> — the marker could not be written. Repair the reported filesystem error, then park it explicitly before changing its worktree state.
  • feature errored — will re-dispatch on the next scan — this was a non-park termination, so no automatic marker suppresses the next scan.

Recovery: fix the underlying setup problem first. Clear the feature’s HALT and HALT.class using the resume procedure, then remove the automatic marker and restore dispatch eligibility:

ai-conductor daemon unpark <slug>

Verification: test ! -e .daemon/parked/<slug> succeeds. The next daemon scan logs ↻ resume <slug> or starts the feature, rather than another parked skip.

3. Re-verify SHIP evidence with --diagnose

--diagnose re-runs the SHIP-gating completion predicates — test_suite, manual_test, finish — against the on-disk evidence and reports which ones cannot reproduce a pass. It does not modify feature state.

ai-conductor inline --diagnose "<feature description>"

Run from the main checkout with the feature description, or from inside the worktree with no description at all (ai-conductor inline --diagnose).

Outcome Output Exit
Evidence consistent State OK: … has consistent SHIP-phase evidence. 0
No state for that description No conductor state found for "<desc>" — nothing to diagnose. 0
Evidence gaps Per-step gap report on stderr, plus remediation guidance 1
State says past worktree but no worktree exists Orphaned conductor state in <path>. 1

A non-zero exit here is the precise signal that the state is marked complete while the evidence that would justify it is missing. That combination is what silently produced “complete” features with no PR.

4. Read the run timeline with --report

ai-conductor inline --report

Read-only. Renders four tables from .pipeline/events.jsonl — Step Durations, Retry Hotspots, Token Spend, and Kickbacks by Source Gate — then exits 0. An unreadable events log exits 1. Run it from inside the worktree; it reads .pipeline/ relative to the current directory.

Known limitation. --report does not render halt tables, although loop_halt, rebase_conflict_halt, and halt_marker_write_failed persist in events.jsonl. For halt occurrences, use cost-rollup.halts, the shipped record’s ## Cost block, ai-conductor kpi, or the engineer-loop signal assembler; use .pipeline/HALT as the durable park state and .pipeline/gates/<step>.json for the gate verdict. Tracked in #1023 and #1008.

5. Read the daemon’s own narrative

ai-conductor daemon logs --lines 200

.daemon/daemon.log carries every dispatch line, per-step result, engine warning, and the startup dashboard snapshot.

Known limitation. The log rotates to .daemon/daemon.log.1 only when it is reopened and already exceeds 1 MB, and no CLI reads the rotated file. A long-running daemon never rotates mid-run; once it does, the previous history is only reachable by opening .daemon/daemon.log.1 directly. Tracked in #1008.

Recovery

Pick the branch that matches the diagnosis. Every step says what it changes.

The stall was a false negative — real work exists

The commits are on the branch but carry no Task: trailer, so the resolved-task count cannot see them and the build completion gate keeps reporting those ids as pending. (The stall breaker itself is not fooled — those commits move HEAD — but the count still drives the gate.) Do not re-run the tasks. Make the work visible instead:

  1. Confirm the commits exist and check their trailers:
    git -C .worktrees/<slug> log origin/<base-branch>..HEAD \
      --format='%h %s%n  Task: %(trailers:key=Task,valueonly)'
    

    Substitute your repository’s default branch for <base-branch>. A blank Task: line means that commit is invisible to the stall breaker.

  2. Flip the corresponding rows in .worktrees/<slug>/.pipeline/task-status.json to "status": "completed". What it changes: the routing input for the build gate. A row already marked completed or skipped is preserved verbatim across every re-seed, so this edit survives.
  3. Confirm the gate now sees them:
    ai-conductor inline --diagnose
    

    and re-read .pipeline/gates/build.json after the next dispatch — its reason should no longer list those task ids as pending.

ai-conductor task done <id> will not help here: it clears the .pipeline/current-task stamp and never modifies task-status.json.

The stall was real — the build genuinely cannot proceed

  1. Read .pipeline/build-stall-question.md (or .pipeline/halt-user-input-required) and act on the question: fix the spec, the plan, or the code.
  2. Remove the user-input marker if it is still present. What it changes: unblocks the build completion predicate, which returns “not done” while the file exists.
    rm -f .worktrees/<slug>/.pipeline/halt-user-input-required
    
  3. Clear the halt (next section).

The halt refused a DECIDE entry

Symptom: .pipeline/HALT begins DECIDE entry refused and .pipeline/HALT.class is needs-human. The body names the source gate, requested target, evidence, and why autonomous entry was refused. The daemon will not re-kick this class of halt.

Recovery: Read that body first. Correct an unknown target, missing artifact, or wrong routing before authorizing anything. When the named DECIDE authoring pass is the intended operator decision, run these commands from any directory inside the repository:

ai-conductor decide-grant --slug <slug> --step <target-from-HALT> \
  --reason "<why this one DECIDE entry is approved>"
rm -f .worktrees/<slug>/.pipeline/HALT .worktrees/<slug>/.pipeline/HALT.class

The first command resolves the main checkout and writes a durable grant for only that target into its daemon-owned .daemon/grants/<slug>.json. Outside a repository, it refuses without recording a grant. The conductor consumes it immediately before provider dispatch, so it cannot authorize another DECIDE step or a later retry.

Never hand-write a grant file. The grant deliberately lives outside the feature worktree: a decide-grant.json written inside .worktrees/<slug>/.pipeline/ authorizes nothing, because that directory is the build agent’s own scratch space and an agent must not be able to authorize itself. Use the command from any directory inside the repository; it resolves the main checkout itself.

plan is never grantable. ai-conductor decide-grant --step plan exits non-zero, and the entry policy refuses plan before consulting any grant. If the HALT names plan as the requested target, the correct recovery is to drive the plan revision yourself — interactively, with /conduct or by editing the plan — and then clear the halt so the feature resumes into BUILD. See the plan-revision recovery below.

Clearing the HALT alone does not authorize entry. Without a valid matching grant, the next poll writes the same needs-human HALT and launches no provider. A grant for stories, for example, does not authorize architecture_review.

Verification: After the daemon resumes, confirm the one-use artifact was consumed and then inspect the result of the named step:

test ! -e .daemon/grants/<slug>.json
ai-conductor daemon logs | grep '\[<slug>\]'

A halt requests a plan revision

Symptom: .pipeline/HALT is needs-human and names plan as the requested target — typically from a remediate or build_review disposition asking for a DECIDE revision.

There is no grant for this. The daemon may not re-plan under any authorization. Recover by hand:

  1. Read the halt body and the remediation that produced it (.worktrees/<slug>/.pipeline/remediation.json) to see which plan change is actually being asked for. Verify the request against the gate evidence — a remediation whose fix would re-trigger the gate that caused it is wrong, and amending the plan to match it makes things worse.
  2. Make the plan edit yourself in the feature worktree. When BUILD discovered that an approved DECIDE assertion must change, add the correction beside the original rather than rewriting it — see amendment requests above. Story artifacts under .docs/stories/ are the exception: replace the superseded assertion in place instead, with no amendment note.
  3. Rewind so the feature resumes into BUILD and actually builds the revised plan, from inside the feature worktree:
    cd .worktrees/<slug> && ai-conductor rewind --to build
    

    ai-conductor rewind marks build and every later non-skipped step stale, clears their gate verdicts, and clears HALT and HALT.class atomically. Do not delete the halt markers by hand: that leaves the already-recorded build, test_suite, and build_review verdicts standing, so the revised plan task is never built. If the plan you edited is sealed — any plan amended after first BUILD entry is — reseal it first, per the protected-artifact recovery.

If the feature is parked while you do this, unpark it last — the daemon re-dispatches as soon as it is eligible.

If the grant remains, the feature did not enter the authorized step; re-read the HALT rather than clearing it again.

The halt is a plan gap

Symptom: .pipeline/HALT.class is plan-gap. prd_audit or the as-built architecture review found an outcome the shipped work does not deliver and no active plan task owns the repair. The approved design is the ceiling here: the daemon retains this halt on every sweep, and clearing the marker without amending an artifact just re-halts on the same criterion. A committed record of the halt is on the feature branch at .docs/halted/<slug>.md; clearing the halt flips its status to resolved.

Blast radius: the amendment rewrites committed DECIDE artifacts on the feature branch and re-runs BUILD and every later step. Nothing outside the feature moves.

  1. Read the gap. .pipeline/prd-audit.md (its per-criterion detail section) or .pipeline/architecture-review-as-built.md (its ## Recorded Findings) names each criterion and why no active plan task owns the repair.
  2. Amend the artifact the gap actually indicts, in the feature worktree — usually the plan (add a task naming the omitted work), sometimes the story (when the criterion overpromises what the approved design can deliver). Update the feature’s coherence table task and criterion rows in the same commit, and commit inside the worktree.
  3. Reseal the amended protected paths from the main repository checkout, not the worktree — a plan or story committed after first BUILD entry leaves the seal baseline stale:
    ai-conductor reseal --slug <slug> --path <path> [--path <path> ...] \
      --reason "<why this amendment is approved>"
    

    ai-conductor reseal is TTY-only by design; run it interactively. See the protected-artifact recovery for what it checks.

  4. Rewind, from inside the feature worktree:
    cd .worktrees/<slug> && ai-conductor rewind --to build
    

    ai-conductor rewind marks build and every later non-skipped step stale, clears their gate verdicts, and clears HALT and HALT.class atomically. Clearing the markers by hand instead leaves build, test_suite, and build_review recorded as done, so the amended plan task is never built.

Two plan gaps need no artifact amendment:

  • The gap is reader-facing documentation the plan deliberately delegated to the maintain-documentation gating step. That step runs after: rebase, downstream of the audit that blocks on the criterion, so it can never clear its own gap. Make the documentation edits by hand — documentation pages are not protected artifacts, so no reseal is needed — then clear the halt by renaming:
    mv .worktrees/<slug>/.pipeline/HALT .worktrees/<slug>/.pipeline/HALT.cleared
    rm -f .worktrees/<slug>/.pipeline/HALT.class
    
  • The gap needs machinery another, unmerged feature owns. Leave this feature halted and sequence the two. Amending this feature’s plan to build the other feature’s deliverable is wrong.

How to confirm: the next dispatch re-enters BUILD rather than re-writing the same halt, and the audit that raised the gap passes its criterion on the following lap. `git show

:.docs/halted/.md` reads `Status: resolved`. ### The remediation planner disagreed with the parsed findings **Symptom:** `.pipeline/HALT.class` is `needs-human` — not `plan-gap` — and the detail reads: ```text Validation group "prd_audit" halted: needs human DECIDE — As-built review remediation planner findings do not exactly match parsed REMEDIABLE findings. Unexpected: S2.4. ``` The remediation planner proposes one gap per finding, and the engine requires that set to match the admitted findings exactly: each one bound either to an as-built `REMEDIABLE` row or to a `prd_audit` finding. The three words name which way it diverged: | Word | Meaning | | --- | --- | | `Unexpected: ` | The planner proposed remediation for an id that is in neither admitted set. | | `Missing: ` | An admitted finding the planner proposed nothing for. | | `Duplicate: ` | The planner proposed the same finding more than once. | **`Unexpected` on a criterion id is the common case, and it is a plan gap.** A criterion graded `PLAN_GAP` in `.pipeline/prd-audit.md` is unmet with no active task owning it, so it is not an admitted finding — but it reads to the planner like work to schedule, and the planner writes tasks for it. Compare the `FIXABLE` rows in the same table: those carry a parent task and are admitted. Watch for one defect graded by both gates. In the example above, the as-built review's only `REMEDIABLE` finding said the same thing as `S2.4` — a story outcome with no runtime path that can produce it — so the planner reasonably wrote a single remediation covering both, and the exact-match check refused it. When the two gates indict one defect, that is a signal the **story** overpromises what the approved design delivers, not that the plan is missing a task. **Recover through [the plan-gap recovery](#the-halt-is-a-plan-gap)** — read the gap, amend the artifact it indicts (usually the story here), update the coherence rows in the same commit, reseal, then `ai-conductor rewind --to build`. `Missing` and `Duplicate` are planner faults rather than plan gaps: no artifact is wrong, so re-dispatch the step and let the planner re-run against the same findings. ### An all-REMEDIABLE as-built review did not route **Symptom:** the `architecture_review_as_built` HALT says `remediation did not route:` and then names one of these causes: remediation is disabled, the run is not in daemon mode, or the planner did not write a usable remediation plan. The HALT also lists the `REMEDIABLE` blocking findings. **Diagnosis:** read `.pipeline/architecture-review-as-built.md` for the finding details. If the cause names the planner, inspect `.pipeline/remediation.json`: it may be absent, stale, invalid JSON, missing a `dispositions` array, or contain no routable disposition. A `DESIGN` finding is different: it requires a human decision and names its governing clause instead of this routing cause. **Recovery:** enable `architecture_review_as_built.remediation.enabled` and re-run in daemon mode when those are the stated causes. Otherwise correct the remediation output so it is current JSON with at least one routable disposition for the listed finding, then use the [resume procedure](#clear-a-halt-and-let-the-feature-resume). **Verification:** the next dispatch either routes the repair work or writes a HALT with a new, specific cause; it must not repeat the same cause after its input has been corrected. ### Worktree preparation failed to install the preventive git hook **Symptom:** the worktree step halts with `preventive git hook installation failed: ` — for example `unable to inspect .git metadata`, `unable to access .git metadata`, or a `writeFile`/git-config error. This is deliberately fail-closed: a worktree with a `.git` present must have the `.pipeline/git-hooks/pre-commit` protected-artifact gate installed and wired before BUILD/SHIP can dispatch into it, so a broken installation halts the run rather than proceeding without the gate. See [settings and hooks](/ai-conductor/reference/settings-and-hooks.html#git-hooks) for what the hook does. Diagnose the underlying filesystem or git-config failure named in `` — a permissions problem on `.git` or `.pipeline/git-hooks/`, a full disk, or a `git config --worktree` failure are the usual causes. Fix it, then clear the HALT and let the daemon re-dispatch; worktree preparation retries the write on the next attempt. A worktree with no `.git` at all is unaffected — that shape has no commit surface to protect and is skipped as a no-op. ### The halt is a protected-artifact violation Do not delete or edit `.pipeline/protected-artifact-seal.json`. The engine rebaselines a stale seal automatically after a clean engine rebase, or during verification when it proves that every changed artifact is byte-identical to the base-branch tip or a recorded remediation append extends a fingerprint-verified sealed baseline. **First, identify an amendment request.** If the halt arose because BUILD discovered that an accepted DECIDE assertion must change, do not amend or reseal it in BUILD. Route the feature back to its owning DECIDE step (the daemon reaches the existing operator gate). There, add the correction beside the original assertion before BUILD starts again: ```markdown > **Amended YYYY-MM-DD by #NNN:** <what the assertion now says, and why> ``` The note is additive: retain the original text and create no separate record. Story artifacts under `.docs/stories/` are the exception: replace the superseded assertion in place with no amendment note. **Annotate history-shaped artifacts; rewrite state-shaped ones.** The additive-note rule above is for artifacts that downstream machinery reads as *history* — a plan's executed tasks are the ledger `prd_audit`'s `FIXABLE` findings match against, so deleting one orphans its diff. But `prd_audit` and the as-built architecture review re-judge the stories' acceptance criteria, the PRD's functional requirements when one exists, and the component diagram as *current state*, comparing what the artifact states verbatim against the shipped implementation. An amendment note asking the reader to substitute new meaning leaves the stated requirement contradicting the code, and every downstream re-judgement risks a finding. For state-shaped artifacts, rewrite the statement outright and keep a one-line dated provenance marker. (Precedent: the 2026-08-14 wiring-rubric retirement — the rewritten FR-1 and diagram passed `prd_audit` all-`PASS` on the first pass; the earlier annotation-only draft would have shipped an FR still claiming five rubrics.) Re-author the plan without a task targeting the other feature's sealed artifact, then run `ai-conductor plan-protected-targets .docs/plans/.md` before landing. A clean result is `No protected-target violations found.`; each violation is reported as `Task : — return this amendment to DECIDE; BUILD tasks must not target protected artifacts.` 1. Read the refusal in `.daemon/daemon.log`: ```bash ai-conductor daemon logs | grep 'Protected artifact rotation refused' ``` 2. Inspect the named path and act on the specific reason: - `Uncommitted protected artifact changed: ` — a workspace edit that was never committed. Restore the file from `HEAD`. - `Protected artifact changed: ` with a `Feature-authored committed change` cause — revert to the committed DECIDE content and route any actual amendment to DECIDE. - `Unvouched engine remediation append: ` — a recorded remediation-task heading is present, but the committed content is not an exact append of either the base-tip or fingerprint-verified sealed content. Review the named content and the reported operator-reseal and engine-append exits; do not treat it as the ordinary feature-authored revert case. - `Protected artifact provenance undeterminable: ` — the base ref could not be resolved, no merge-base exists between `HEAD` and the base branch, or the inheritance probe (`git diff`) failed. Supply the base ref, or rebase onto the base branch to establish shared history, then retry. - Anything else — resolve the reported baseline/base-tip lookup failure. A safe inherited base-branch change (including one where the base has since moved past what this feature's last rebase brought in, as long as this feature never touched the path) needs no manual seal repair; the next sanctioned rebase or verification rotates it. 3. Clear `HALT` and `HALT.class` using the next procedure. If the cause remains, verification refuses again before dispatch. **The edit is intentional and operator-approved (e.g. a feature's plan or architecture was amended mid-build after the first BUILD seal was created).** The committed amendment makes the existing seal baseline stale. The engine has no automatic path for this — a feature-authored change never rotates on its own, by design, so there is no default action to take here without a human reading the diff first. Do not hand-edit `.pipeline/protected-artifact-seal.json` (malformed JSON or a wrong fingerprint silently breaks verification for every other artifact in the seal). Instead, review the diff, commit the amendment, and only after approving it, run [`ai-conductor reseal`](/ai-conductor/reference/cli.html#ai-conductor-reseal) from the main repository checkout so it recomputes real fingerprints instead of guessing at them: ```bash ai-conductor reseal \ --slug \ --path <path under .docs/... that was reviewed and approved> \ --reason "" \ --clear-halt ``` Repeat `--path` for each amended file. The command only runs from an interactive operator terminal — it refuses under a step subprocess or any non-TTY invocation — and refuses the whole reseal if any protected path outside the given `--path` list has also drifted, so it cannot be used to launder unrelated changes into the seal. On success it writes a new baseline at the current commit, appends a `rebaselines` entry recording the trigger (`operator-reseal`) and rationale, and writes a `protected_artifact_reseal` audit record with an `operator` origin — so the override is auditable rather than silent. `--clear-halt` also clears the worktree's HALT in the same step, once its class is the protected-artifact class — see [operator-authorized protected-artifact reseals](/ai-conductor/explanation/gates.html#operator-authorized-protected-artifact-reseals). The reseal survives later rebaselines. When the base branch subsequently moves and the seal rebaselines onto the feature's new merge base, a resealed path is no longer refused as a feature-authored DECIDE change: it is kept at its approved content and reported as `kept operator-resealed paths: ` in the daemon log. That approval is bound to the content it was taken against, not to the path — amend the artifact again after resealing and it refuses exactly as before, because the sealed fingerprint no longer matches. Reseal again only after reviewing the new amendment. For a changed path in the feature's coverage-binding DECIDE set, a successful reseal also invalidates completed coverage evidence. On the next re-kick, coverage binding runs before BUILD. Its amendment judgement may reopen a completed task only when the amended obligation contradicts that task; a reseal by itself never reopens work. Inspect the emitted coverage-binding events and the refreshed `.pipeline/coverage-binding.json` rather than editing task state by hand. If REKICK encounters this refusal before starting git, the HALT begins `protected-artifact seal error` and explicitly says no rebase is active. Do not use the rebase resolver or run `git rebase --continue`; review and rotate the seal as above, then clear the HALT and re-queue. ### A rebase never started after an untracked-file collision **Symptom:** `.pipeline/HALT` begins `rebase did not start — parked for human recovery`. It may list `Quarantined files:` and a `Quarantine directory:` under `.pipeline/rebase-untracked-quarantine`. No rebase is active; do not run `git rebase --continue`. **Diagnosis:** Git refused to begin because an untracked worktree path would be overwritten. For that exact refusal, the daemon confirms that each named path is still untracked, moves the confirmed files into the quarantine directory without overwriting an earlier entry, and retries once. The HALT means the retry or another pre-start check still could not proceed. **Recovery:** Park the feature before changing its worktree, then inspect the halted marker and any quarantined content. Preserve or restore only content that is still needed; do not put a colliding file back at its original path before the next rebase can succeed. ```bash ai-conductor daemon park cd .worktrees/ cat .pipeline/HALT find .pipeline/rebase-untracked-quarantine -type f -print git status ``` After resolving the stated refusal, return to the main checkout, clear both halt files, and unpark using [the resume procedure](#clear-a-halt-and-let-the-feature-resume). The next daemon dispatch re-queues the feature; it does not continue a rebase. ### A flattened rebase was refused **Symptom:** `.pipeline/HALT` is `needs-human` and begins `flattened rebase refused`. Its recovery procedure names a merge sha, the merge's first parent, and a `git rebase -i --rebase-merges` command. No rebase is active. **Diagnosis:** The feature branch contains a content-bearing merge, and its flattened commit conflicts with the new base. The engine detected this before mutation, so the branch is unchanged; see [merge-bearing feature branches](/ai-conductor/guides/running-the-daemon.html#merge-bearing-feature-branches). The persisted `rebase_conflict_halt` event's `mergeAudit` field lists the merge's parents, its flattened sha, and the conflicting paths. **Recovery:** Run the recipe from the HALT, substituting its values: ```bash ai-conductor daemon park git -C .worktrees/ rebase -i --rebase-merges # At the merge stop, re-apply the merge's own changes shown by: git -C .worktrees/ diff # Resolve and stage them, then: git -C .worktrees/ rebase --continue ``` Do not use `reset --hard` or `checkout -- `. When the rebase completes, clear both halt files and unpark using [the resume procedure](#clear-a-halt-and-let-the-feature-resume). A merge-bearing branch whose proof fails before a merge can be named parks with `rebase did not start` instead; recover it as in [a rebase never started](#a-rebase-never-started-after-an-untracked-file-collision). ### The completed rebase halted for missing feature content **Symptom:** `.pipeline/HALT` is `needs-human` and begins `rebase completed — parked for human review`. Its reason lists up to three missing commit subjects, each with its abbreviated pre-rebase identity and the failed content evidence; a suffix states how many further subjects were omitted. The rebase has already completed, so do not run `git rebase --continue`. The rebase work-preservation guard requires every pre-rebase commit subject to survive the replay. A commit legitimately vanishes when the base already carries its work: the replay empties it and Git discards it. That happens whenever the same fix lands independently on `main` first — the feature's own copy conflicts, the resolver settles it in the base's favour, and nothing remains to commit. The guard therefore checks each vanished commit's intent before reporting loss: every line it added must be present in `HEAD`, and no line it removed may be back (counted against the commit's own parent, so a structural line like `});` surviving elsewhere in the file does not count as restored). A commit whose work is genuinely absent and cleanly re-appliable still halts. **Recovery:** park the feature, review the named evidence, and restore any missing work: ```bash ai-conductor daemon park cd .worktrees/ git show ORIG_HEAD -- # inspect the named pre-rebase content git status # must be clean before clearing the halt ``` If the evidence is correct, restore the missing feature content from `ORIG_HEAD`, commit the repair, and confirm the working tree is clean. If the content is already present through an equivalent base change, verify that equivalence before clearing the halt. Then remove both live halt files, verify they are absent, and unpark as described in [A completed rebase still appears halted](#a-completed-rebase-still-appears-halted). ### A completed rebase still appears halted **Symptom:** `git rebase --continue` completed successfully, but the dashboard still lists the feature under HALTED and both `.pipeline/HALT` and `.pipeline/HALT.class` remain. Manual Git commands complete the rebase; they do not reconcile the daemon's pipeline markers. 1. Park the feature before inspecting or changing its git state: ```bash ai-conductor daemon park ``` Keep it parked through marker cleanup and verification. Do not unpark before clearing the stale halt; that makes the feature dispatchable while its recovery state is inconsistent. 2. From the feature worktree, prove that no rebase is active and the checkout is clean: ```bash cd .worktrees/ git status test ! -d "$(git rev-parse --git-path rebase-merge)" test ! -d "$(git rev-parse --git-path rebase-apply)" test -z "$(git status --porcelain)" ``` Stop if either rebase state directory exists, `git status` reports a rebase, or the porcelain output is non-empty. Finish and verify the rebase before removing any marker. 3. Return to the main checkout and remove both live halt files: ```bash cd ../.. rm -f .worktrees//.pipeline/HALT .worktrees//.pipeline/HALT.class ``` Removing only `HALT` leaves a stale classification sidecar and does not complete recovery. 4. Verify that the rebase state directories and both live halt files are absent, then unpark: ```bash test ! -d "$(git -C .worktrees/ rev-parse --git-path rebase-merge)" test ! -d "$(git -C .worktrees/ rev-parse --git-path rebase-apply)" test ! -e .worktrees//.pipeline/HALT test ! -e .worktrees//.pipeline/HALT.class ai-conductor daemon unpark ``` The next dashboard snapshot should list the feature under ELIGIBLE or IN-PROGRESS rather than PARKED or HALTED. The daemon log should show `↻ resume ` after dispatch. ### Clear a halt and let the feature resume **Blast radius:** clearing the halt makes the feature eligible for dispatch again on the next poll. Fix the cause first, or it halts again immediately. #### Audited clear From an interactive terminal anywhere in the repository, record the authorization and clear the marker pair in one step: ```bash ai-conductor halt clear --feature --rationale "" ``` The command appends `halt_clear_authorized` to the event spine and audit trail before it touches any marker, and refuses without clearing if that record cannot be written. It handles the `over-scope` rename below for you; edit the decisions block first. For a budget-cap halt, prefer [`kickback-budget`](#kickback-loops): a direct clear changes no budget, so the feature re-halts at its next cap check. See [`ai-conductor halt clear`](/ai-conductor/reference/cli.html#ai-conductor-halt-clear) for flags and exit codes. The manual commands below remain valid but leave no authorization record. #### OVER_SCOPE decision halt If `HALT.class` is `over-scope`, do not clear the body unchanged. Edit the fenced `over-scope-decisions` JSON array in `.pipeline/HALT`: for each decision you are making, set `decision` to `accept` or `refuse` and add a non-empty `rationale`. Leave entries you are not deciding as `pending`. An accept clears that criterion; a refusal records the decision but keeps the halt active as “refused — rework required.” Keep the offered criterion and summary unchanged, including when a revision refers to an older report number or wording. Only the decision and rationale are editable. A revision entry left `pending` keeps the prior decision unchanged. A revision that re-affirms the prior decision (for example, `refuse` on an existing refusal) is inert: it records nothing new and does not fail the recovery. Only a changed decision creates a new revision. For a durable PRD-widening recovery, keep every unaffected decision and refusal in place. The halt body names the affected source, case, decision, or artifact and one of these actions: - `malformed-history`: restore the original widening history from a known-good copy, then resume. - `unsupported-history`: preserve the history and upgrade to a conductor version that supports it. - `missing-operator`: configure the machine owner and resubmit the explicit decision. - `persistence-failed`: resolve the store or lease failure and verify the durable records. - `invalid-decision`: correct the named entry against the latest halt, or leave its decision `pending` to keep the prior decision, then clear the halt. - `invalid-provider-result`: retry only once the selected provider supports the required native schema. - `stale-relation`: retain the decision and rerun reconciliation against the current report. - `context-overflow`: the halt and event name the dimension, actual size, and limit; reduce the cited source input without pruning history. - `projection-failed`: repair the named stored evidence or verdict renderer without re-deciding valid authority. - `uncertain-relation`: review the preserved original and current evidence, then submit a new explicit decision if desired. Version-2 `accepted-widenings.json` preserves valid legacy attributed evidence in its original order. A refusal is also preserved: revise it only through its offered explicit revision entry, never by reusing an old accepted clear or deleting history. Then clear with `ai-conductor halt clear` — never `rm -f` the body. For an over-scope halt the command renames the edited body to `.pipeline/HALT.cleared`; the next prd_audit lap harvests your decisions from `HALT.cleared` and from nowhere else, so deleting the body silently discards every decision you just authored and the feature re-halts with the same blocking set: ```bash ai-conductor halt clear --feature --rationale "" ``` **What it changes:** the daemon registers a filesystem watcher on each halted feature's marker and wakes when it is cleared, so removal is the resume signal. Filesystem events are the fast path; the watcher also polls (every second by default), so a clear that lands before the watcher is ready — or an event the OS drops — is still picked up within one poll interval. (With `--no-watch` the daemon relies on its own polling loop instead; it still picks the feature up, just on the next poll.) **How to confirm:** the next dashboard snapshot lists the slug under ELIGIBLE or IN-PROGRESS rather than HALTED, and the log shows `↻ resume `. ### A cleared FINISH halt resumes to a recorded ship **Blast radius:** none beyond the feature. Clearing the halt re-enters FINISH; the coordinator re-observes every publication boundary and performs only the transitions still outstanding. Clear the halt with [the resume procedure](#clear-a-halt-and-let-the-feature-resume). Nothing else is required, and nothing should be repaired by hand: - The daemon re-dispatches the feature even when `.docs/shipped/.md` is already committed on its branch. That record is written by the mid-sequence `write_shipped_record` transition, so on its own it proves one transition ran, not that the ship completed. The shipped-record dedup therefore skips a candidate only when FINISH **recorded its outcome** (`.pipeline/finish-choice` in the worktree) or the worktree is already gone. A retained worktree with no outcome record is resumed. - FINISH authors the PR prose itself. If the body is still the engine-seeded placeholder, the coordinator dispatches its `author_pr_prose` pass with the branch diff and the feature's spec artifacts; you do not need to write the body. - The resumed run records the outcome, enrolls the PR in `.daemon/mergeable-watch.jsonl`, and the mergeable sweep reaps the worktree after the shipped record is proven on the default branch. **How to confirm:** the daemon log shows `re-dispatch : shipped record is on this feature's branch but FINISH recorded no outcome …`, then the FINISH publication transitions, and finally the enrollment. If it instead logs `skip : shipped dedup — … awaiting the human merge`, FINISH *did* record an outcome (`.pipeline/finish-choice` exists) — the work really is complete and waiting on your merge. ### An auth park timed out Refresh the credential the halt body names, then clear the halt as above. Re-check with `ai-conductor build-auth-status` before clearing — a halt cleared against a still-broken credential just re-parks and burns the timeout again. ### A rate-limit episode is in progress Do nothing. The wait is deliberate and does not consume the retry budget. A Claude notice such as `You've hit your weekly limit · resets 9pm (America/New_York)` waits until its stated reset (up to one day); session and usage-limit notices retain the hourly re-probe cap. If you must stop the daemon during an episode, see [emergency stop a running feature](/ai-conductor/runbooks/emergency-stop-a-running-feature.html) — and note that halts raised during the episode will not be auto-recovered by the replacement process. ### Codex could not create a process for its shell tool calls The dispatch output names the failure directly: ```text Codex could not create a process for 4 shell tool calls (its tool router reported `exec_command failed ... CreateProcess`). ``` Codex uses its provider-native sandbox for each shell tool call. When the host denies that sandbox capability, every `exec_command` is rejected while Codex itself still exits 0 and still answers. The engine classifies such a dispatch as a failure rather than a result, so a `build_review` rubric that could not run `git diff` reports an infrastructure failure instead of a hollow PASS. **Diagnose the host, not the feature.** Check `daemon status` for `READ-ONLY REVIEW CAPABILITY` and the provider-specific reason. Until that capability is available, route the affected steps to another provider or record reduced coverage; clearing the halt alone re-runs into the same denial. ### build_review has a scope-incomplete candidate **Symptom:** `build_review` reports a mechanical fault whose cause is `scope-incomplete`; after the shared mechanical-fault allowance is exhausted, `.pipeline/HALT` names `testQuality` and `scope-incomplete`, then lists one line per uncovered candidate: `scope-incomplete :- (): `. The diagnostic identifies the concrete candidate, its available marker/obligation evidence, and the evidence that remains missing. **Diagnosis:** this is not an instruction to add a test merely because a test path appears in the plan, and it is not a test-insensitive finding. The engine already formed a frozen, feature-local candidate from a changed declaration with an ambiguous `Covers` association or from identified changed setup/helper evidence affecting an opted-in test or group. The reviewer returned a valid `indeterminate` scope resolution. Any valid findings from the same review remain in `.pipeline/build-review.json`; a scope fault does not discard them. **Recovery:** resolve the named ambiguity with source evidence where possible. The operator may supply a scope clarification, authorize a binding or evidence correction within the approved work, or authorize separately scoped analyzer work. Do not create speculative plan tasks, add a marker solely to silence the fault, or retry unchanged indeterminacy indefinitely. If the shared allowance is exhausted and the operator explicitly accepts reduced test-quality coverage, use the reduced-coverage procedure below for the named `testQuality` rubric and lap, then clear the halt. That acceptance suppresses only the derived scope fault: address any retained test-quality finding through its normal disposition or repair path. **Verification:** the next review either contains a complete candidate resolution or renders the operator's reduced-coverage decision. A valid finding from the original result is still listed and still blocks unless independently repaired or accepted. ### build_review halted on a projection-oversized rubric **Symptom:** `.pipeline/HALT` names `projection-oversized`, the rubric, and usually both the measured and configured byte limits. The matching `build_review_rubric_infrastructure_failure` event carries `measuredBytes` and `limitBytes` when both values were available. **Diagnosis:** the canonical UTF-8 projection exceeded that rubric's positive-integer `max_projection_bytes` limit. The engine does not dispatch the reviewer, does not retry the same snapshot, and does not consume the shared mechanical-fault allowance. **Recovery:** first decide whether a larger configured limit is appropriate for the feature's review corpus. If it is, change `build_review.rubrics..max_projection_bytes` through the approved configuration path and resume. Otherwise, after explicitly accepting reduced coverage, record it for the named current lap and rubric, then clear the halt: ```bash ai-conductor build-review record-reduced-coverage --feature --lap \ --rubric --rationale "<why this rubric's coverage is being reduced>" ``` **Verification:** the command accepts the named current `projection-oversized` fault without requiring the mechanical-fault allowance to be exhausted. After the resume procedure, the next aggregate either uses the changed limit or records the reduced-coverage decision; any independent semantic finding still blocks normally. ### build_review halted on an exhausted mechanical fault allowance **Symptom:** `.pipeline/HALT` reads: ```text build_review mechanical fault allowance exhausted: of 3 shared faults consumed. Current lap : closed cause (). 1. Record a reduced-coverage decision: ai-conductor build-review record-reduced-coverage --feature --lap --rubric --rationale "". 2. Clear the documented terminal state: rm -f .pipeline/HALT .pipeline/HALT.class. ``` When the current lap has no readable aggregate (for example, its verdict came from an earlier lap and the completion guard scored it `absent`), the halt instead reads: ```text build_review mechanical fault allowance exhausted: of 3 shared faults consumed; current-lap diagnostic is unavailable; Last recorded fault: closed cause on lap (). ``` naming the ledger's `lastMechanicalFault` record — the most recent rubric/reason/lap the allowance was actually charged against — instead of the numbered recovery steps. Recovery is the same: record reduced coverage for the named rubric and lap, then clear the halt. Both forms carry class `needs-human` — deliberately, so the daemon's automatic re-kick sweep does not clear it on its own. **Diagnosis:** a `build_review` rubric repeatedly produced either an infrastructure failure (a tool crash, a `git diff` that could not run, or a provider outage) or a valid `scope-incomplete` outcome from an indeterminate concrete candidate. Mechanical faults draw from a bounded allowance shared across `build_review` kickbacks (3 faults), tracked separately from the ordinary semantic kickback budget so an infrastructure blip or unresolved scope cannot be laundered into a hollow PASS or an endless kickback loop. The allowance resets only on demonstrated progress (a rebase or base-branch advance), not on a bare retry. When the grader subprocess exited without any classifiable result, the cause is `invalid-provider-result` and `` names the bounded exit facts: ```text build_review grader invocation ended without a result. subprocess exited without a classifiable result: exitCode= signal= stdoutBytes= stderrBytes= ``` Only present fields are printed. A signal outside the known set prints as `UNKNOWN`; a signal termination with no exit code omits `exitCode=`. The same line is written to `daemon.log`, and the failed attempt's `step_retry` / `step_failed` events carry it as `providerExit` ([event reference](/ai-conductor/reference/artifacts.html#pipelineeventsjsonl)). Use it to tell an external kill (for example `signal=SIGKILL`) from a provider crash (`exitCode=1` with empty output) before recording reduced coverage. **Recovery:** the halt body names both required steps. 1. Record the decision — this requires an interactive terminal and a resolvable local operator identity; it derives the closed infrastructure cause itself, so it refuses if the rubric is not currently an exhausted infrastructure failure or `scope-incomplete` outcome (already judged, unknown rubric, allowance not actually exhausted, a duplicate reduced-coverage record, or the lap/review has since gone stale): ```bash ai-conductor build-review record-reduced-coverage --feature --lap \ --rubric --rationale "<why this rubric's coverage is being reduced>" ``` This only writes the decision — it does not touch the halt marker. 2. Clear the halt using [the resume procedure](#clear-a-halt-and-let-the-feature-resume). **Verification:** the next `build_review` run treats the named fault as reduced coverage rather than halting on the same infrastructure failure or `scope-incomplete` outcome, the feature can reach PASS, and the shipped record at `.docs/shipped/.md` carries a reduced-coverage section for that rubric. A genuine semantic FAIL on the same rubric still blocks exactly as before — recording reduced coverage does not suppress a real finding. **The record stops the halt, not the dispatch.** An excused rubric is still dispatched on every later lap and can still report the same infrastructure failure or scope-incomplete outcome; what changes is that the named fault no longer halts the run. Expect to keep seeing the corresponding build-review fault events in `.pipeline/events.jsonl`. That is not a sign the record failed to take; confirm the record itself in `.pipeline/build-review-dispositions.json`. ### Remediation supplied no recognized disposition **Symptom:** `.pipeline/HALT` begins with `remediation planner returned no recognized disposition:` and `.pipeline/HALT.class` reads `needs-human`. The remainder names every dropped gap as ` → ""` and gives the accepted vocabulary. **Diagnosis:** inspect `.pipeline/remediation.json`. Each listed gap has a missing, non-string, or unrecognized `disposition`, so the engine rejects it instead of silently treating it as build work. It emits one `remediation_disposition_rejected` event per rejected gap to both `.pipeline/events.jsonl` and `.pipeline/audit-trail/events.jsonl`. A mixed remediation plan still routes its recognized gaps; only the rejected entries are dropped and reported. **Recovery:** correct the remediation output so every intended gap uses one of the accepted dispositions named in the halt, then clear the halt using [the resume procedure](#clear-a-halt-and-let-the-feature-resume). Do not clear the halt first: the unchanged remediation output will halt again. **Verification:** the next run either routes the recognized remediation work or halts for the documented reason belonging to a valid `halt` disposition. The rejected-disposition event is absent unless the corrected plan still contains an invalid entry. ### Remediation names an evidence-complete owning task **Symptom:** a remediation `build` disposition names an existing plan task whose prior `Task:` trailer or task-status row says it is complete. **Diagnosis:** this is valid remediation work, not a terminal no-work condition. The conductor persists a repair obligation with the finding, owning task, and the current commit boundary; it then re-stages that task and returns to BUILD. Earlier completion evidence cannot close this repair. ```bash python3 -c " import json d = json.load(open('.worktrees//.pipeline/remediation.json')) for x in d['dispositions']: print(x['id'], x['disposition'], '|', x['rationale'][:120]) " ``` **Recovery:** normally, take no operator action. Let BUILD address the finding and record current completion evidence. If the run halts instead, use the stated refusal: an unreadable repair state, an unavailable post-admission commit boundary, or a task-status re-stage failure needs repair before the feature resumes. Do not clear a halt merely to retry unchanged state. A halt reading `remediation produced no dispatchable build work; the implicated task(s) are already evidence-complete` on a feature that plainly has an open obligation used to mean the obligation was being ignored: repair state was keyed on `engine-state.json`'s `activePlanPath`, which only the interactive plan step ever writes, so a daemon-dispatched, spec-landed feature had none and the old `Task:` trailer re-closed the re-staged task. Repair reads now resolve the plan through the same ladder the obligation writer uses (recorded path, then the slug-scoped convention). If the plan cannot be resolved at all while obligations exist, the refusal says `no active plan could be resolved` — restore the feature's `.docs/plans/.md` (its stem must equal the feature slug) rather than clearing the halt. **Verification:** the named task is dispatched in BUILD, and the repair closes only after current evidence and the governing review pass. A later, distinct finding creates a new repair boundary; restarting the conductor replays an admitted open repair rather than treating the earlier completion as sufficient. ### The feature must stop being dispatched entirely ```bash ai-conductor daemon park ``` **What it changes:** writes `.daemon/parked/` at the main repo root, which is checked before every dispatch and first in the re-kick sweep. Full procedure and the ordering rules are in [emergency stop a running feature](/ai-conductor/runbooks/emergency-stop-a-running-feature.html). ## Verification 1. **No live halt marker remains** for a feature you intended to resume: ```bash ls .worktrees//.pipeline/HALT 2>/dev/null || echo "no halt" ``` 2. **SHIP evidence re-verifies** — from inside the worktree: ```bash ai-conductor inline --diagnose ``` Expect exit 0 and `State OK:`. A non-zero exit still names the failing steps. 3. **The blocking gate now passes.** Re-read `.pipeline/gates/.json` after the next dispatch: `satisfied` must be `true` and `checkedAt` must be newer than the halt you cleared. 4. **The task count moves.** After the next build attempt, the resolved-task count in `.pipeline/task-status.json` (unioned with `Task:` trailers) must be strictly higher than it was, and the log must not repeat `build_stall`. 5. **The feature reaches a terminal outcome.** The daemon log shows `■ done : done` with a PR link, not another `halted`/`error` line. If the feature ships but the daemon keeps re-dispatching it, the shipped record is missing — see [shipped record reconciliation](/ai-conductor/runbooks/shipped-record-reconciliation.html).

This site uses Just the Docs, a documentation theme for Jekyll.