Relationships
#3307 A signal by key can be answered pending or taken when that is not what happened to it
Opened by zack · 10/12/2026· Shipped 10/12/2026
Problem
Follow-up to swamp-club#3212 (early signals, acceptEarly), raised by the adversarial and code reviews of PR 3021. Two replies of workflow signal by key can say something other than what happened to the signal.
1. pending for a signal the opening wait then refuses
When a signal arrives while a step is opening its wait (the key is claimed, the wait is not registered yet), arriveEarly in src/libswamp/workflows/signal.ts answers pending on the assumption that the step takes the stored record when it reads its slot (the held case with no registration).
pendingOutcome in src/domain/workflows/pending_signal.ts can still refuse it:
receivedAtis earlier than the claim'searly.from: the gap between the signal and the claim exceededacceptEarly. Easy to hit at small values.- The run captured a schema that differs from the current definition and refuses the payload.
authorizedFordiffers from the workflow name the run recorded (a rename, or a copy that shares the ID).
The step then suspends with the signal in its own slot. No later claim reads that slot, and every reader of this wait applies the same refusal, so the signal is never taken and the wait runs to its deadline. The sender was told it is held.
2. taken always says no resume is needed
landedAnswer sets awaitingResume: false for a taken answer. findTakerOf only checks that the claim's outcome carries this signal's receipt, not who wrote the outcome. If another reader (workflow waits, or serve's takePendingSignalsOfRun) gives the pending signal to a wait that is already suspended, between the signaller's second read and findTakerOf, the reply is taken. The CLI prints "its run went on without waiting" and shows no resume command, though the run is suspended. Under swamp serve with auto-resume the run is continued anyway; locally the user is not told to resume.
Related: renderRemoteSignalResult tells a caller with signal alone "The signal takes effect when the run is next resumed" even for a signal the step took on opening, because the server sends taken only to callers who may read the workflow.
Suggested fix
- Before answering
pendingin theheld-and-unregistered case, evaluate the stored record against the claim'searlyand the workflow name aspendingOutcomewill. If it would not be taken, fall through todeliverToHolderor answer with a clear refusal. - Compute
decideContinuation(run, store)fortakenas theduplicateanswer already does, instead of hard-codingfalse. Either sendtakento every caller (it names no run) or make the signal-only sentence neutral.
Add tests for each case; neither is covered today.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.