Skip to content

fix(run-engine): stop task retries consuming the queue nack budget - #4810

Merged
matt-aitken merged 3 commits into
mainfrom
fix/retry-requeue-nack-budget
Aug 28, 2026
Merged

fix(run-engine): stop task retries consuming the queue nack budget#4810
matt-aitken merged 3 commits into
mainfrom
fix/retry-requeue-nack-budget

Conversation

@matt-aitken

@matt-aitken matt-aitken commented Aug 28, 2026

Copy link
Copy Markdown
Member

Summary

A run whose task retries were delayed long enough to go back through the queue could end up failed with TASK_RUN_DEQUEUED_MAX_RETRIES and status SYSTEM_FAILURE even though every attempt had actually executed. The real failure from the final attempt was replaced by that placeholder error, and tasks configured for more retries than the queue redelivery limit never got them.

Root cause

Retries with a delay at or above the warm-start threshold are requeued via tryNackAndRequeue, which nacks the queue message. nackMessage increments the message attempt counter by default and dead-letters the message once it reaches the queue retry limit. That counter is meant to bound redeliveries of a run that never comes back healthy; a task retry after a worker-reported attempt failure was being charged against it anyway, so a long-backoff retry schedule exhausted it.

Fix

nackMessage gains a resetAttemptCount option that zeroes the counter instead of incrementing it. tryNackAndRequeue exposes it as resetQueueAttempts, and the attempt-retry path passes it only when the worker reported the failure itself (!forceRequeue). Engine-detected stalls (heartbeat timeouts, which are requeued through the same path with forceRequeue), dequeue failures and stalled PENDING_EXECUTING snapshots keep consuming the budget, so a run that keeps stalling is still bounded by the queue redelivery limit as before.

Tests cover the queue-level reset (no dead-letter at the limit) and an engine-level run that retries past the queue limit and finishes with its own error rather than a system failure. The existing heartbeat timeout tests pin the stall path.

A task retry whose delay is long enough to go back through the queue was
counted as a failed dequeue, so after enough retries the run was dead-lettered
and failed with TASK_RUN_DEQUEUED_MAX_RETRIES even though every attempt had
actually executed. The queue attempt counter is now reset on that path, since
a completed attempt proves the run can start.
@changeset-bot

changeset-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c98465b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Task retries now reset the queue dequeue-attempt counter when the worker reports a failure. Engine-detected stalls and dequeue failures keep consuming the redelivery budget. RunQueue.nackMessage supports the reset mode and avoids dead-lettering the message. Tests cover direct queue behavior and four task attempts with a queue retry limit of two. A changelog entry documents the fix.

Merge Risk: 🔵 Low · up to c9846

The change prevents worker-reported task retries from exhausting the queue redelivery budget while retaining limits for genuine stalls. It is mergeable with owner awareness that separate telemetry for reset versus consumed queue attempts would improve production visibility; no merge-blocking correctness or availability risk remains.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 4…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing task retries from consuming the queue nack budget.
Description check ✅ Passed The description provides clear summary, root cause, fix details, and test coverage. It does not use all template headings or include an issue reference, but it contains the main information required f…
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 4 files.

Full details: Description check

Explanation

The description provides clear summary, root cause, fix details, and test coverage. It does not use all template headings or include an issue reference, but it contains the main information required for review.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/retry-requeue-nack-budget

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@matt-aitken
matt-aitken enabled auto-merge (squash) August 28, 2026 01:48
coderabbitai[bot]

This comment was marked as resolved.

The requeue-processing job is debounced to fire before the retry becomes due
and no consumer runs after it in this test, so a dequeue that happened to run
first found nothing.
Only a failure the worker reported itself resets the counter. A heartbeat
timeout is requeued through the same path, and a run that keeps stalling still
has to be bounded by the queue redelivery limit.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Devin Review

Comment thread internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts (2)

1129-1129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add crumbs to the new retry-budget path.

Mark the new reset-versus-consume logic with // @crumbs`` or an @crumbs region. The markers are removed by `agentcrumbs strip` before merge.

As per coding guidelines: Add crumbs as you write code, not just when debugging.

Also applies to: 1289-1289

Source: Coding guidelines


1129-1129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an OTEL metric for the queue-attempt outcome.

RunAttemptSystem forwards the reset decision to RunQueue.nackMessage, which either resets or consumes the attempt budget. Existing queue metrics emit only generic nack and dlq operations. Add a counter with bounded outcome values: reset and consumed. Do not include IDs or error text as attributes.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bd2fb169-06df-4a8d-bd95-6fe750a2b9a1

📥 Commits

Reviewing files that changed from the base of the PR and between fb264d3 and c98465b.

📒 Files selected for processing (1)
  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.

📜 Review details
⏰ Context from checks skipped due to timeout. (20)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (14, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 24)
  • GitHub Check: internal / 🧪 Unit Tests: Internal
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (1, 2)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (2, 2)
  • GitHub Check: code-quality / code-quality
🧰 Additional context used
📓 Path-based instructions (6)
**Prefer static imports over dynamic imports.** Only use dynamic `import()` when:

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
Add crumbs as you write code — not just when debugging. Mark lines with

📄 CodeRabbit inference engine (AGENTS.md)

Files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
Integrate OpenTelemetry tracer and meter instrumentation in RunEngine systems for observability

📄 CodeRabbit inference engine (internal-packages/run-engine/CLAUDE.md)

Files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
Use function declarations instead of default exports

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
Use types over interfaces for TypeScript

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

Files:

  • internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts
🔇 Additional comments (1)
internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts (1)

1253-1253: 🗄️ Data Integrity & Integration

No change is needed for resetQueueAttempts.

The worker failure path passes resetQueueAttempts: !forceRequeue; ordinary retries pass true. Stalled and dequeue failure paths omit the option and retain false.

@matt-aitken
matt-aitken merged commit 1f8f230 into main Aug 28, 2026
54 checks passed
@matt-aitken
matt-aitken deleted the fix/retry-requeue-nack-budget branch August 28, 2026 17:12
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.

3 participants