fix(orchestrator): report the steps a failed dependency stopped - #1275
Conversation
A task still pending when the drain ends never ran and never will: a dependency failed, so `nextRunnable` can never return it. Nothing marks it terminal, so it emits no completed/skipped/failed event at all — a step the user was offered and accepted disappears from the funnel rather than showing up as an outcome. The abort message had the same gap, reporting only how many steps never ran and not which. Emit `orchestrator task blocked` per still-pending task at drain end, and name those steps in the abort message. The queue is left as it is: the run cache is already wiped by this point, and writing to it would recreate the folder the cleanup just removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: e3b8a0c8-f668-4172-950b-91242cb09e59
🧙 Wizard CIRun the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands: Test all apps:
Test all apps in a directory:
Test an individual app:
Show more apps
Test against a Context Mill branch:
Add Results will be posted here when complete. |
|
Overlaps textually with #1013 (stale automated draft, no commits since 2026-07-28), which edits the same abort line for a different reason: it splits the guard into two exception names and fixes the |
Brings in the eight main fixes since beccc79 (#1275, #1280, #1284, #1294, #1295, #1301, #1302, #1305). One conflict: agentic detection keeps main's two-attempt retry loop from #1305 and adds A1's progress emitter to the agent it initializes. Generated-By: PostHog Desktop Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…int aborts The orchestrator's post-drain guard raised `orchestrator drain ended with failed tasks` on a drain where nothing failed but tasks stayed blocked behind a dependency that never completed. The user-facing copy already named the steps, so only the exception misnamed itself — and it sent triage looking for a failure the queue state does not contain. A small pure `drainFailureError()` now returns the stable exception name per cause (mirroring the pi harness's completion guard), so a blocked-only drain groups under its own name instead of "failed tasks". Separately, every abort's captured exception was fingerprinted server-side from its stack trace, which carries the install path — pnpm dlx content hashes, `.npm/_npx` dirs, CI checkouts, local clones. One cause therefore shattered into an issue per install, each reading "1 occurrence, 1 user, 1 session". Aborts are raised from a fixed set of guard sites, so `wizardAbort` now supplies a stable message-derived `$exception_fingerprint`, following the pattern already proven on the OAuth flow. A callsite that wants different grouping can still set the property in its `WizardError` context. Rebased onto main after #1275, which merged into the same guard. This keeps its per-task `orchestrator task blocked` events, the `describeDrainFailure` message that names both the failed step and the steps it stranded, and the missing-scope copy — and reconciles the guard down to the one thing #1275 left unsolved: the split exception name. The user-facing text and pluralisation now come from `describeDrainFailure`, so `drainFailureError` returns only the name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Problem
A task that is still
pendingwhen the orchestrator drain ends never ran andnever will: one of its dependencies failed terminally, so
nextRunnablecannever return it. Nothing marks it terminal, so it emits no
completed,skipped, orfailedevent — the step simply leaves no trace.That makes an accepted step vanish from the funnel. The seeded data-source step
is given dependencies on the planned coding tasks, so a terminal failure
upstream of it strands it: the user was offered the step, accepted it, and the
outcome is then unrecorded rather than counted. Telemetry for the last day
shows runs ending this way, including one where the accepted data-source step
left no outcome at all.
The user-facing message had the same gap. The abort reported
N steps never ran— a count with no names, so someone who had just agreed to connect theirdata sources could not tell whether that was one of them.
Why: this came out of reading the seeded-task funnel. Every other terminal
state is recorded with a reason; this one is the hole, and it is the hole an
accepted step falls through.
Changes
drainVerdictalso returnsblockedTypesalongside the existingblockedcount.
orchestrator task blockedevent fires once per still-pending task atdrain end, carrying the task type, whether it was optional, and the required
types that failed. Additive: no existing event or property changes, and the
queue is not mutated — the run cache is already wiped by this point, and
writing to it would recreate the folder the cleanup just removed.
describeDrainFailurecomposes the abort line from both halves, so themessage names the step that failed and the steps it stopped.
Test plan
src/lib/agent/runner/sequence/orchestrator/__tests__/optional-tasks.test.tsfor
blockedTypes(including a stranded optional seeded step) and for allthree
describeDrainFailureshapes.the touched files.
tsc --noEmiterror count unchanged frommain(31 pre-existing, none inthe touched files).
Created with PostHog Desktop
🤖 Generated with Claude Code