Skip to content

Gate resume continuations behind the execution limits - #1873

Draft
ux-xd wants to merge 1 commit into
UsefulSoftwareCo:mainfrom
ux-xd:fix/meter-resume-continuations
Draft

Gate resume continuations behind the execution limits#1873
ux-xd wants to merge 1 commit into
UsefulSoftwareCo:mainfrom
ux-xd:fix/meter-resume-continuations

Conversation

@ux-xd

@ux-xd ux-xd commented Aug 30, 2026

Copy link
Copy Markdown

Summary

withPreExecutionGate (shared by the balance gate and the rate-limit backstop) now gates resume in addition to execute / executeWithPause, and each resume counts against the hourly rate-limit counter.

The bypass

One execution can pause many times — every elicitation is a pause point, and each pause mints a new execution id. Both pre-execution guards passed resume through untouched:

  • execution-gate.ts — "resume is never gated: a paused execution already consumed its quota slot when it started"
  • execution-rate-limit.ts — the counter only increments on execute calls
  • execution-usage.ts — "resume doesn't count as usage"

The quota-slot assumption only covers the first pause. A single billable executeWithPause whose code loops over an approval-gated tool drives an unbounded chain of pauses and resumes, and every continuation between the pauses is fresh sandbox work and tool calls with no balance check and no rate-limit increment after the first execution. The only bounds are the per-pause approval TTL and session idle eviction, so one free-tier quota slot could carry arbitrary amounts of continued computation.

The fix

A blocked decision on resume answers with the same descriptive error a blocked execute returns and never invokes the inner engine, so:

  • nothing runs un-gated,
  • the paused execution stays intact and the human's approval decision is never consumed by a refused attempt,
  • approved work completes once the org is back under its limit.

Usage tracking is left as-is (a resume is still not billed as a new execution) — this PR is about enforcement, not billing semantics.

Verification

The new execution-resume-gate.node.test.ts fails on the previous tree — a spent-balance resume and an over-cap resume both ran to completion — and passes with the fix:

  • balance gate refuses a resumed continuation when the quota is spent; the paused execution stays inspectable afterwards
  • each resume increments the rate-limit counter; an over-cap continuation is refused
  • an allowed balance still reaches the engine (gating, not stranding)

vitest run src/engine/execution-resume-gate.node.test.ts src/engine/execution-rate-limit.node.test.ts — 17 passed. turbo run typecheck --filter=@executor-js/cloud — 20/20 tasks green.

Not run: e2e (narrow scenario still to be picked per the repo contract if reviewers want one; the changed seam is covered at the decorator level here).

One execution can pause many times, and every resume continues the
sandbox with fresh code and tool calls. Both pre-execution guards
passed resume through untouched, so a single billable
executeWithPause that paused in a loop drove an unbounded chain of
continuations past the balance gate and the rate limiter: one quota
slot, zero further checks.

Gate resume in withPreExecutionGate like the other execution paths: a
blocked decision answers with the same descriptive error a blocked
execute returns and leaves the paused execution intact — nothing runs
un-gated, and approved work completes once the org is back under its
limit. Each resume now also counts against the hourly rate-limit
backstop.

The regression test fails on the previous tree (a blocked-balance and
an over-cap resume both ran to completion) and pins the fix.
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