Skip to content

fix(runtime): resume --retry-failed leaves a continuing task its started consumers ran without (#1231) - #1254

Merged
aviggiano merged 5 commits into
unstablefrom
fix/1231-retry-failed-optional-omission
Oct 2, 2026
Merged

aviggiano merged 5 commits into
unstablefrom
fix/1231-retry-failed-optional-omission

Conversation

@mrthankyou

@mrthankyou mrthankyou commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1231.

Problem

A failed node in a failure_policy: continue group (a property lens since #1198, or an optional strategy) is an optional input to its consumers in other groups, so they run without it. resume --retry-failed reran it anyway. A succeeded consumer is never finalized again, so the rerun could not reach that consumer's output. Once the rerun succeeded, every planned task read as succeeded, the report said COMPLETE, and a --require-complete run could end succeeded.

Confirmed on unstable (a375d3b4): with the new tests and no fix, resume --retry-failed issued timetravel --node-id node:lens (and node:optional-specialist) after the consumer had finished without it.

Fix

Takes the direction the issue proposes:

  • packages/runtime/src/retry-failed-omissions.ts: failedProducersStartedConsumersOmitted names each failed producer that a started consumer lists in optionalDependencyArtifactDirs. It has two readers:
    • The resume reader works from Smithers node states. A consumer counts as started once its prepare:, node: or verify: node is in-progress, finished, failed or stalled.
    • The Modal reader works from state.json and smithers/tasks.json.
  • runSmithersLifecycleCommand: --retry-failed no longer resets those producers. It still retries every other failed task. resumeRun reports each skipped producer as a WORKFLOW_RETRY_SKIPPED warning that names the consumers that ran without it. The report stays PARTIAL.
  • Modal: modalDurableRunNeedsResume takes the retained failures and no longer resumes a terminal run whose only failures are those. Before, waitForTerminalRun would wait for a change that never came.
  • Docs: added the new behavior to docs/reference/cli.md and a CHANGELOG entry.

Not changed: the report-completion schema, and --reset-node, which can still rerun such a node.

Tests

  • New runtime tests (runtime.test.ts) cover a lens and a strategy. Each runs with its consumer started (producer left failed, other failed task retried, warning reported) and with its consumer not started (producer retried as before).
  • New assertion in the existing sync test "syncRun finalizes consumers admitted without an optional prerequisite whose retry is still running": after the first sync, readFailedProducersStartedConsumersOmitted reports lens → [catalog].
  • New Modal unit test for modalDurableRunNeedsResume with retained failures.
  • Targeted runtime tests: 13 of 13 pass, including the existing --retry-failed tests and the sync tests for optional prerequisites. Modal resume.test.ts: 32 of 32 pass. eslint and prettier are clean on the changed files. I have not run the full runtime suite locally.

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

The PR is not safe to merge until Modal avoids resuming terminal runs whose only non-retained failed checkpoint entries are skipped tasks.

Fix All in Claude CodeFindings

  1. P1 Skipped tasks trigger needless resume ▶
Fix with agent prompt
### Issue 1
packages/modal/src/resume.ts:74-77
When a terminal run has a retained failed producer and a skipped downstream task, the checkpoint counts the skipped task as failed. This check therefore requests a resume, even though `--retry-failed` cannot reset the skipped task and leaves the producer failed. With nothing to redo, the Modal worker waits for a state change until its watch times out instead of finalizing the partial evaluation.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR keeps failed optional producers failed once consumers have started without them, reports skipped retries, and uses retained-failure state to decide whether Modal should resume a terminal evaluation.

  • Adds runtime omission detection and retry filtering, with lens, strategy, reset, and aggregate tests.
  • Updates Modal resume decisions and documents the resulting partial-run behavior.
  • The Modal decision still mistakes skipped tasks for work that --retry-failed can redo.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Terminal run] --> B[Read retained producer failures]
  B --> C{Other failed checkpoint IDs?}
  C -->|Retryable task| D[Resume and wait for advancement]
  C -->|None| E[Finalize partial evaluation]
  C -->|Skipped task misclassified| D
Loading

Reviews (4) · Last reviewed commit: "Merge branch 'unstable' into fix/1231-re..."

…ted consumers ran without (#1231)

A started consumer admitted a failed continuing producer as an optional
omission, so rerunning the producer could not reach that consumer's output.
Once the rerun succeeded every planned task read as succeeded and the report
said COMPLETE. `--retry-failed` now leaves such a producer failed, warns with
WORKFLOW_RETRY_SKIPPED, and retries the other failed tasks. Modal no longer
resumes a finished run whose only failures are such producers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mrthankyou
mrthankyou requested a review from a team as a code owner October 1, 2026 15:39
…p non-null assertions from its tests

A property lens is an optional input to every task after the property
fan-in (50 in the default topology), so the warning now names three and
counts the rest. The new resume tests guard the fake command log with
assert.ok instead of `!`, which lint:strict forbids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread packages/modal/src/resume.ts
Comment thread packages/runtime/src/start-run.ts
thankyou and others added 2 commits October 1, 2026 11:22
… in retained retries (#1231)

Modal read a fanned-out lens's aggregate state entry, and an expanded
dynamic group's entry, as a failure `--retry-failed` would rerun, so it
could resume a run with nothing to retry and wait for a change that never
came. readRetainedFailureStateIds now includes such an entry when every
entry under it that did not succeed is retained.

A task `--reset-node` resets is no longer reported as left failed in the
WORKFLOW_RETRY_SKIPPED warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…PPED where the warning is built

Filtering it inside runSmithersLifecycleCommand pushed that function past
the lint complexity limit on the branch merged with unstable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mrthankyou

Copy link
Copy Markdown
Collaborator Author

Real-engine reproduction of the scenario this PR fixes, where the report claimed COMPLETE with 0 issues while the strategies that found the bug were never read: #1231 (latest comment).

Resolve the CHANGELOG.md conflict by keeping both entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@aviggiano
aviggiano merged commit 6b93d6f into unstable Oct 2, 2026
4 of 5 checks passed
@aviggiano
aviggiano deleted the fix/1231-retry-failed-optional-omission branch October 2, 2026 11:12
Comment on lines +74 to +77
const failedNodeIds = Object.entries(state.nodes ?? {})
.filter(([, node]) => node.status !== undefined && CHECKPOINT_FAILED_STATUSES.has(node.status))
.map(([nodeId]) => nodeId);
return failedNodeIds.length === 0 || failedNodeIds.some((nodeId) => !retainedFailures.has(nodeId));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Skipped tasks trigger needless resume When a terminal run has a retained failed producer and a skipped downstream task, the checkpoint counts the skipped task as failed. This check therefore requests a resume, even though --retry-failed cannot reset the skipped task and leaves the producer failed. With nothing to redo, the Modal worker waits for a state change until its watch times out instead of finalizing the partial evaluation.

Knowledge Base Used: Modal worker integration

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/modal/src/resume.ts
Line: 74-77

Comment:
**Skipped tasks trigger needless resume** When a terminal run has a retained failed producer and a skipped downstream task, the checkpoint counts the skipped task as failed. This check therefore requests a resume, even though `--retry-failed` cannot reset the skipped task and leaves the producer failed. With nothing to redo, the Modal worker waits for a state change until its watch times out instead of finalizing the partial evaluation.

**Knowledge Base Used:** [Modal worker integration](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/modal-worker-integration.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

resume --retry-failed can report COMPLETE after rerunning a continuing node its consumers already ran without

2 participants