fix(labeler): dispatch the assessment Workflow on operator rerun#2103
Conversation
runRerun minted a fresh assessment run, gated the release with assessment-pending, and advanced the run to pending in its deferred tail, but never dispatched the Cloudflare Workflow that executes it — stranding the release pending forever until the reconciliation stuck-run alert. ConsoleMutationDeps lacked the workflow binding entirely. Thread AssessmentWorkflowBinding into ConsoleMutationDeps (wired from env.ASSESSMENT_WORKFLOW), and dispatch the run in deferRerunTail after advancing to pending, keyed on the run's runKey. The operator trigger yields a fresh runKey, so the rerun gets its own instance; a re-driven tail or replay dedups onto it via the instance-id lock.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-do | 57b442c | Jul 17 2026, 09:32 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 57b442c | Jul 17 2026, 09:33 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 57b442c | Jul 17 2026, 09:33 PM |
There was a problem hiding this comment.
This is a focused, correct bug fix.
Approach judgment: The PR targets the right problem — operator reruns were minting fresh runs and gating releases with assessment-pending, but never dispatching the AssessmentWorkflow, leaving runs stranded. The fix threads the existing ASSESSMENT_WORKFLOW binding into ConsoleMutationDeps and dispatches the run in deferRerunTail after advancing it to pending. This mirrors the discovery-consumer path, reuses the runKey-as-instance-id idempotency invariant from #2072, and keeps the dispatch off the response path consistent with other post-commit side effects (afterCommit, notifications).
I verified that:
ASSESSMENT_WORKFLOWis already declared inwrangler.jsoncandworker-configuration.d.ts; the PR only wires the binding into console mutations.dispatchAssessmentWorkflowis called withrunKey: run.runKeyandassessmentId: runId, matching the discovery path.- Both the proceed and replay branches recompute the same trigger/runKey so the Workflow instance id is stable across replays and dedups correctly.
- The dispatch failure is intentionally caught and logged (documented reliance on the stuck-run sweep) without surfacing to the client.
- All
ConsoleMutationDepsconstruction sites (production and tests) were updated with a workflow binding. - The package is
private: true, so the "no changeset" checkbox is justified.
The test additions cover the happy path, replay idempotency, and dispatch-failure posture, and the default mutationDeps mock won’t break non-rerun tests. No logic bugs, AGENTS.md convention violations, or missing wiring found.
Approve with no blocking comments.
7c4b847
into
feat/plugin-registry-labelling-service
What does this PR do?
Fixes a functional bug in the operator Re-run an assessment action: it minted a fresh run and gated the release with
assessment-pending, but never dispatched the Cloudflare Workflow that executes it — so the release was stranded inpendingindefinitely (until it surfaced as a reconciliation stuck-run alert). Targets thefeat/plugin-registry-labelling-serviceintegration branch (RFC #694, umbrella #1909).Root cause:
ConsoleMutationDepsnever carried theASSESSMENT_WORKFLOWbinding at all, sorunRerun's deferred tail (deferRerunTail) advanced the run topendingand published the pending label but structurally could not dispatch. The discovery path got the binding and dispatches (discovery-consumer.ts); the console path never did — almost certainly because rerun was built while the analysis stages were stubbed and PROD-gated, and the dispatch wasn't reconnected when the real stages landed (#2090/#2091). The bug became live-visible only once the pipeline started producing real assessments.Fix: thread
AssessmentWorkflowBindingintoConsoleMutationDeps(wired fromenv.ASSESSMENT_WORKFLOWat the console-mutation construction site), and dispatch the run indeferRerunTailafter advancing topending, keyed on the run's persistedrunKey:Why it's safe (the dedup rests on #2072's instance-id lock):
runKey(from theoperator:<actionId>trigger, distinct from discovery'sinitialTriggerId(cid)and any prior rerun), so it dispatches its own new Workflow instance — no collision with the original run's.runKey, so a re-driven tail (adeferretry, or the guardMutation replay branch — which also runs this tail) re-dispatching the samerunKeydedups to"exists": no duplicate run, no duplicate labels.Dispatch-failure posture (documented): the dispatch stays inside the existing guarded
deferRerunTailtry/catch — a failure is logged and dropped. Unlike discovery (which retries the queue message), this deferred tail has no retry lever, so a dropped dispatch strands the runpendinguntil the reconciliation stuck-run sweep surfaces it for a manual re-trigger. A comment states this. Not escalated to anoperational_eventfor a rare infra failure that the stuck-run alert already backstops — flagging in case you'd prefer immediate operator visibility.Type of change
Checklist
pnpm typecheckpasses (all three labeler projects)pnpm lintpasses (oxlint type-aware, clean on all changed files)pnpm testpasses (rerun + discovery + dispatch suites 53; full labeler suite 833) — including TDD: the dispatch-assertion tests fail against pre-fix code (expected [] to have length 1, proving dispatch was never invoked) and pass post-fix.pnpm formathas been run@emdash-cms/labelerisprivate: true.AI-generated code disclosure
Screenshots / test output
New tests: a rerun dispatches its fresh run to its own Workflow instance (id === runKey, params.assessmentId === runId); proceed + replay yields exactly one created instance (idempotent); a tail dispatch failure keeps the mutation a 200 and doesn't crash the tail.
Try this PR
Open a fresh playground →
A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.
Tracks
fix/labeler-rerun-dispatch. Updated automatically when the playground redeploys.