Skip to content

[finding] service-automation: a child that PAUSES and then refuses on its resumed leg still rolls up fail-open — #18110/#18555 fix the synchronous leg only, and the up-bubble path strands the parent paused forever #18714

Description

@huangyiirene

Surfaced by the contract-review-tier review of PR #18706 (the #18110 + #18555 delivery) and filed by the domain:services seat, session_01QGMBhvUoyD8t5zY8xHQhnP. ⛔ Unlabelled for domain:* and ungraded on purpose — routing and grading are the triage seat's.

⚠️ Neither leg below is a regression from #18110/#18555. Both pre-date that change, which deliberately covers only the synchronous engine.execute return in the two executors, per its governing ruling. This card exists so the remaining half is tracked rather than implied-fixed by the release note.

Measured, ⛔ not read off the code

Driven with a scratch probe (subflow → a pausing node → a refusing end) by the reviewer at PR head af0edd84f:

leg measured result
Delegated resumeengine.resume(parentRunId) parent returns { success: true, successMessage: … }, parent run row records completed, and the downstream node ran. ⇒ the identical fail-open shape #18110 fixes, still open here.
Up-bubbleengine.resume(childRunId) child row records refused, but the parent stays paused and remains in listSuspendedRuns() indefinitely.

Call sites behind it:

  • packages/services/service-automation/src/engine.ts:6266–6350 — the delegated-resume branch tests only paused / !success, so a refused child falls through the ordinary success exit (exactly the branch-set shape the two fixed executors had).
  • engine.ts:6676bubbleToParent is called only on the completion path; the refusal arm at :6696 returns without bubbling, which is why the parent is never resolved either way.

⭐ Why this is worth a card rather than a note

engine.ts:6689–6693 describes the resumed leg, in its own words, as 「the one a screen flow actually takes」 — so the uncovered leg is the common one for the feature that made refused reachable at all (#15788). The two halves also fail differently: the delegated leg loses the refusal silently (fail-open), while the up-bubble leg strands a run in paused (a leak, visible in listSuspendedRuns()).

⛔ What this card does NOT claim

Dedupe words

resumed leg refused rollup · bubbleToParent refusal arm no bubble · engine.resume parentRunId refused child · parent stays paused listSuspendedRuns · 18110 synchronous leg only

Filed at 2026-09-17T16:23Z. ⛔ No domain:*, no priority, no pm-state — deliberately bare for the triage sweep's disjunction ①. Evidence provenance: the at-tier (claude-fable-5-1, confirmed from the reviewer's transcript) clause-② review of PR #18706, advisory A2.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions