Skip to content

fix(execution): stop run-buffer failures from stranding resumed executions - #6187

Merged
waleedlatif1 merged 4 commits into
stagingfrom
fix/run-buffer-resume-resilience
Aug 2, 2026
Merged

fix(execution): stop run-buffer failures from stranding resumed executions#6187
waleedlatif1 merged 4 commits into
stagingfrom
fix/run-buffer-resume-resilience

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • A failed run-buffer terminal event publish no longer decides the outcome of a resume whose workflow already ran. The buffer is a replay convenience for stream readers; the durable execution record is authoritative. Previously the failure threw, the resume was marked failed, and the execution stayed paused permanently — and because that path is not retryable, the next attempt re-ran the workflow's side effects. It now degrades: records the terminal status on the stream meta so readers aren't left polling an active stream, logs, and lets the resume settle on its real result.
  • Per-user Redis byte budget keys now get a fixed window instead of a sliding one. The TTL was refreshed on every accepted write, so for a user who never went a full TTL without writing, the key never expired — while the per-execution data it accounted for did. The counter accrued bytes Redis had already dropped and drifted to the ceiling, after which every write for that user was rejected until they went a full TTL idle. Applied across all five Lua scripts that touch the key so the writers can't drift apart.
  • Execution-scoped budget keys keep sliding — they're refreshed on the same schedule as the data they account for, so counter and bytes stay in step.
  • Drops the now-unreachable TERMINAL_PUBLISH_ERROR constant and its branch. Pre-execution buffer failures stay fatal and retryable, since no work has happened yet.

Type of Change

  • Bug fix

Testing

  • New apps/sim/lib/execution/redis-budget.server.test.ts plus two cases in event-buffer.test.ts. Both new guards verified to fail when the fix is reverted.
  • tsc -p apps/sim, biome, and check:api-validation pass.
  • 649 tests pass across lib/execution, lib/workflows/executor, lib/uploads, and app/api/resume.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…d execution

The run buffer is a replay convenience for stream readers; the durable
execution record is authoritative. A failed terminal event publish was
deciding the outcome of work that had already run: it threw, the resume
was marked failed, and the paused execution was left paused forever.

That path is also not retryable — the workflow had already executed — so
the next resume attempt re-ran its side effects.

Degrade instead. Record the terminal status on the stream meta so readers
are not left polling an 'active' stream, log the failure, and let the
resume settle on its real result. The same treatment applies when the
terminal event is never published at all, which previously synthesized an
error for the same stranding effect.

Removes the now-unreachable TERMINAL_PUBLISH_ERROR constant and its
branch. Pre-execution buffer failures stay fatal and retryable, since no
work has happened yet.
The per-user byte budget refreshed its TTL on every accepted write, so for
any user who never went a full TTL without writing, the key never expired.
The per-execution data it accounted for kept expiring underneath it, so the
counter accrued bytes Redis had already dropped and drifted toward the
ceiling. On reaching it, every subsequent write for that user was rejected
until they stopped writing for a full TTL — and since a rejected write does
not refresh the TTL, it recovered on its own and then refilled.

User keys now get a fixed window: the TTL is set when the key is created and
never extended. Applied to all five Lua scripts that touch the key so the
writers cannot drift apart. Execution-scoped keys keep sliding — they are
refreshed on the same schedule as the data they account for, so their
counter and bytes stay in step.
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 2, 2026 4:11am

Request Review

@cursor

cursor Bot commented Aug 2, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes resume settlement and Redis budget TTL semantics across multiple Lua scripts; incorrect TTL logic could reject writes or leak counters, while resume degradation must not mask real execution failures before work runs.

Overview
Resumed workflows no longer fail or stay paused when the run buffer cannot publish a terminal replay event after the work has already finished. The durable execution record stays authoritative; on publish failure the code degrades by writing terminal status to stream metadata via setExecutionMeta, warning in logs, and completing the resume—avoiding stranded paused state and duplicate side effects on retry.

Reconnect SSE now ends with data: [DONE] when metadata is terminal but no terminal event exists in the buffer (degraded replay), instead of erroring the stream.

Per-user Redis byte budget keys use a fixed TTL window: EXPIRE on the user key only when TTL < 0 (set at creation), so active users do not slide the window forever while per-execution data expires underneath and inflates the counter to the ceiling. Execution-scoped budget keys still slide on each write. Same TTL guard is applied consistently across event-buffer flush, reserveExecutionRedisBytes, base64 cache Lua, and stream reset scripts.

Removes the unreachable TERMINAL_PUBLISH_ERROR path; pre-execution buffer unavailability remains fatal and retryable.

Reviewed by Cursor Bugbot for commit 7bb2ca6. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes terminal run-buffer publication failures nonfatal after resumed workflow execution and consistently applies fixed TTL windows to per-user Redis budgets while retaining sliding execution-scoped budgets.

  • Records degraded terminal status in stream metadata and lets resumed executions settle according to their durable result.
  • Ends reconnect streams cleanly when terminal metadata exists without a buffered terminal event.
  • Aligns user-budget TTL behavior across reservation, event-buffer, and upload-cache Lua scripts.
  • Adds coverage for fixed user windows, sliding execution windows, and degraded terminal replay.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported user-budget concern was resolved in-thread as an intentional bounded rate-window tradeoff, and the current Lua paths consistently implement that design while deleting nonpositive counters atomically.

Important Files Changed

Filename Overview
apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts Degrades terminal event publication failures into metadata updates and logging rather than failing an already-executed resume.
apps/sim/lib/execution/redis-budget.server.ts Changes user budget counters to fixed TTL windows while preserving sliding execution-budget expiration.
apps/sim/lib/execution/event-buffer.ts Applies the fixed user-budget window consistently across event-buffer growth, shrinkage, no-op refresh, and reset paths.
apps/sim/lib/uploads/utils/user-file-base64.server.ts Aligns upload-cache user-budget cleanup and replacement paths with fixed-window expiration.
apps/sim/app/api/workflows/[id]/executions/[executionId]/stream/route.ts Closes degraded terminal streams with the normal completion marker instead of surfacing a replay error.

Reviews (3): Last reviewed commit: "fix(stream): end a replay cleanly when t..." | Re-trigger Greptile

Comment thread apps/sim/lib/execution/redis-budget.server.ts
The no-op branch of the flush script dropped the user-key EXPIRE outright
rather than guarding it like every other site. No current path can create
the key without an expiry, but if one ever did, that branch was the one
place that would never give it one — and a user counter with no expiry is
the unbounded version of the bug this series fixes.

Guard it instead, so the branch heals such a key rather than skipping it,
and so all five scripts read the same way.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 4c7df92. Configure here.

…nal event

Terminal metadata is the authoritative end-of-run signal — the happy path
writes it atomically with the terminal event, and a run whose terminal
event could not be buffered records the status on its own. The reader
required both, so the degraded case threw and turned a replay that was
merely incomplete into a broken one.

Metadata without a matching event means the buffer degraded, not that the
run is still going. Log it and close the stream: the reader has already
received every event that was buffered, and the durable record is
unaffected either way.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7bb2ca6. Configure here.

@waleedlatif1
waleedlatif1 merged commit 69289d2 into staging Aug 2, 2026
27 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/run-buffer-resume-resilience branch August 2, 2026 04:44
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.

1 participant