Relationships
#4438 swamp-lib: workflowRun never settles when a run suspends at an approval gate
Opened by skunk-ape · 11/5/2026
Summary
SwampClient.workflowRun() in @swamp-club/swamp-lib never settles when the run suspends at a manual_approval gate. A client that awaits it hangs indefinitely, even though serve has finished the request.
Reproduction
- Run
swamp servewith a workflow whose first step ismanual_approval. - Call
await client.workflowRun({ workflowIdOrName: "gated" }, { suspended: () => { /* fires */ } }).
Seen on abeba3f6a with swamp-lib 0.2.0 from packages/client, under Node 26, against a local serve in token mode.
| Call | Result after 10 s |
|---|---|
workflowRun |
suspended handler ran; the promise had not settled |
workflowRunStream |
yielded through suspended, then ended correctly |
The frames serve sends over the raw WebSocket:
validating_inputs, evaluating_workflow, started, job_started, step_started, approval_requested, suspended, followed by a done frame.
Cause
In packages/client/client.ts, handleMessage resolves on the completed and cancelled events and rejects on error, but not on suspended. The done frame then deletes the pending entry and calls pending.queue?.done(). That ends a stream, but workflowRun has no queue, so its promise is dropped without settling.
Expected
workflowRun settles when a run suspends, in a way the caller can tell apart from a finished run. The suspended event carries run, so resolving with it looks natural, though that return shape is a decision for the fix.
A done frame should also never leave a pending promise unsettled, since that hides any other case that reaches it without a terminal event.
Impact
Any integration that starts gated workflows through swamp-lib's main run helper hangs at the first approval gate, which is the case approval integrations exist for.
Workaround: settle from the suspended handler. workflowRunStream does end at a gate, but it drops refusals (#4445), so it is not a safe substitute on its own.
Open
No activity in this phase yet.
Sign in to post a ripple.