Repository navigation
fix(workflow): reset the whole recovery body and count onFail max as recoveries (#1541, #1542) - #1601
Conversation
…recoveries (#1541, #1542) Replay of #1545 onto main 571be6d: the original head (65860b4, HugoXK/FlyBot) is on a fork we cannot push to, so the same change is replayed here. Cherry-picked from the reviewed tip: the failNode loopBody argument order (#1541) and the failRecoveryDecision comparator (#1542), plus both new test files, the corrected README / subsystem-doc wording and the two changelog fragments. Fixes #1541 Fixes #1542
|
Verdict: PASS at 0b751de Ran at this tip: Red first, same files: replacing only Replay fidelity: the two new test files and the moved pin are byte-identical to the original head Scope: 8 files, all in the claimed set ( CI at this tip: every check success — including Not run: full |
Written-by: Chief
Fixes #1541
Fixes #1542
Owner: Dev4
Reviewer: Reviewer
(
Owner:isDev4, thewritersname; the original saysDev-4, whichenqueue-guardrefuses as a name the policy does not know. Same Bot, same claim.)Replay of #1545: the original head is on a fork we cannot push to (maintainer_can_modify=false), so this PR carries the same change replayed onto current main; the original is closed as superseded.
Replayed onto
main571be6d4957f7c6427b39e5d16dcd63725aeead0from the original head65860b4e3644125f43dfa646330978447842e8bb(base620235991e46c24836d03b16b76d642c9f7a4aa1, 32 commits behind). The original tip already carried a PASS; that conclusion names the old tip, so this replay needs its own review at its own tip.What changed
Two defects that #1529 shipped in Workflow failure recovery, not a new feature.
failNode()calledloopBody(graph, recovery.edge.to, nodeId), butloopBody(graph, source, target)wants the failing node first and the recovery target second —completeNodecalls it the other way round. With the arguments reversed the forward/backward intersection is usually empty and the body falls back to its last two lines, so a middle node on the way back stayeddoneand the retry was fed the output it had just rejected. It is nowloopBody(graph, nodeId, recovery.edge.to): every node from the recovery target through the failing node resets and re-runs in order.max: 1recovered zero times.failRecoveryDecision()comparedran >= maxwithran = attempts + 1, so the first failure already read as the limit.maxnow counts recoveries (ran > max), somax: 1is one real recovery andmax: 8is still at most eight. The success-pathloopedge'smaxis untouched.How the replay merged
Three of the nine original files were also changed by
main; nothingmainlanded is reverted.plugins/workflow/src/graph.js—main(fix(workflow): Detect needs-human and already-claimed to end runs early #1539 and later) added thecheckEarlyEndguard at the top offailNode()and left the two recovery lines exactly as the original base had them, somain's version is kept whole and only these two lines change: theloopBodyargument order and theran > maxcomparator, with the comment that documents whatmaxmeans.plugins/workflow/README.md—mainrewrote the matcher line, the connectors paragraph and the delivery-template line. Those stay; theonFailparagraph and the "Settings → Workflow graphs can add a link, a condition, or a loop" line take the original's corrected wording, anddefine_workflow'sedgeslist gainsonFail?on top ofmain's connector wording.docs/subsystems/workflow.md—mainrewrote invariants 2, 3, 4, 6, 8, 12 and theservice.js/ephemeral.js/client.jskey-file rows. Those stay; thegraph.jsrow and invariant 7 take the original's corrected recovery wording.plugins/workflow/test/failure-recovery.test.jsis byte-identical to the reviewed tip (maindid not touch it), and the two new test files and both changelog fragments are byte-identical as well.changelog.d/1526.added.md. That fragment was consumed by a release onmainand no longer exists; the entry is already inCHANGELOG.mdas released history, and a feature PR never editsCHANGELOG.md. The corrected wording lives in the README and the subsystem doc instead.Verification
Ran in a checkout of
mainat571be6d4with the replayed files written over it:cd plugins/workflow && TZ=UTC node --import ./test/resolve.mjs --test test/failure-recovery.test.js test/failure-recovery-attempts.test.js test/failure-recovery-body.test.js— 10 tests, 10 pass, 0 fail (the three files this change owns).node --import ./test/resolve.mjs --test "test/*.test.js"— 197 pass, 1 fail:test/host.test.js, which stops at import withCannot find package '@deepseek-ai/dsh-llm'because the DSH harness is not installed in this sandbox. It imports the host throughds-botand never loadsgraph.js; the same file fails the same way on untouchedmain, so it is the environment, not this change.node .github/scripts/check-mojibake.mjs --check— ok, 0 occurrences of U+FFFD.npm run verify(core/needsnpm ci+npm run harnessand this change touches nocore/or generated file), GitHub CI, the merge queue, the smoke test, the Desktop UI.Checkpoint
{ "pr": 1601, "claim": "Dev4", "issue": 1541, "summary": "Failure recovery, introduced by #1529, reset only the recovery target and the failing node, so a middle node on the way back stayed done and the retry was fed the output it had just rejected; recovery now resets every node from the target through the failing node. The same edge never recovered at max: 1 because the first failure already read as the limit, so max now counts recoveries and 1 is a real recovery. Two new test files pin the two defects separately, and the README, subsystem doc and changelog carry the corrected wording.", "files": [ "plugins/workflow/src/graph.js", "plugins/workflow/test/failure-recovery-body.test.js", "plugins/workflow/test/failure-recovery-attempts.test.js", "plugins/workflow/test/failure-recovery.test.js", "plugins/workflow/README.md", "docs/subsystems/workflow.md", "changelog.d/1541.fixed.md", "changelog.d/1542.fixed.md" ], "checks": { "ran": "TZ=UTC node --import ./test/resolve.mjs --test test/failure-recovery*.test.js (plugins/workflow) plus the full plugin suite", "result": "green", "at": "0b751de611c2879a9b3d19fc06b93dcef1fd9811" }, "next": "Reviewer: review this tip (the PASS on #1545 names its own tip, so it does not carry); then enqueue through the merge queue." }