Relationships
#2432 workflow resume prints unexpected errors as a fatal stack trace, and --json returns them unclassified with the stack
Opened by hammz · 9/23/2026· Shipped 9/24/2026
Problem
When swamp workflow resume fails with an error that is not a UserError, it prints as a fatal error with a full stack trace. swamp workflow run reports the same errors as a single line.
With the same bad input on the same workflow (a step argument of ${{ inputs.code + 0 }} and a string code):
swamp workflow run <wf> --input code=notanumberprintsError: Workflow execution failed: Invalid expression: no such overload: dyn<string> + intswamp workflow resume <wf> --run <id> --from <step> --input code=notanumberprints[FTL] error: InvalidExpressionError: Invalid expression: no such overload: dyn<string> + intfollowed by about 20 lines of stack, and a nested[cause]block with its own stack.
--json mode differs the same way:
runwrites{"error": "Workflow execution failed: ...", "code": "workflow_execution_failed"}.resumewrites{"error": "...", "stack": " at CelEvaluator.evaluateAsync (file:///tmp/deno-compile-swamp/src/...)..."}with nocode, so a caller cannot classify the failure and gets internal source paths instead.
Any error from the resume stream that is not a UserError shows up this way. Another way to hit it is --from on a step that was renamed since the run failed, which prints [FTL] error: Error: Step run not found: <step> with a stack trace. The --from behavior itself is a separate problem.
This affects suspended resumes, --from, and the retry of failed runs added by swamp-club#2409, on main (c811f8ce) and on that branch.
Cause
src/cli/commands/workflow_resume.ts consumes the resume stream inside try/finally with no catch, so these errors reach the top-level handler, which prints anything that is not a UserError as fatal with its stack. src/cli/commands/workflow_run.ts catches errors from its execution stream and rethrows them as UserError("Workflow execution failed: …").
Fix approach
Handle errors from the resume stream the way workflow run does: rethrow them as a UserError with a resume-specific message and error code, so log mode prints one line and --json mode returns a classified error without a stack. Check the --server path for the same gap.
Reproduction
- Create a workflow with step
prep, then a stepcheckthat depends on it and whose shellrunisexit ${{ inputs.code + 0 }}. swamp workflow run <wf> --input '{"code":3}':checkfails with exit code 3. Note the run id.swamp workflow resume <wf> --run <id> --from check --input code=notanumber(on the #2409 branch,--fromcan be dropped).- The resume exits 1 with an
[FTL]InvalidExpressionError and stack trace. Add--jsonto see the{"error","stack"}shape.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.