Skip to main content
← Back to list
01Issue
BugShippedSwamp CLI
Assigneesstack72

Relationships

#1558 serve boot OOM: findAllGlobalSince loads all workflow runs as full aggregates during reconciliation

Opened by stack72 · 8/7/2026· Shipped 8/8/2026

Summary

swamp serve crashes with a V8 OOM (~4088 MB) during boot at the "reconciling workflow run state" phase. The root cause is findAllGlobalSince at serve.ts:2097 loading every workflow run from the last 7 days as a full WorkflowRun aggregate — including the complete jobs[] -> steps[] -> output -> dataArtifacts[] tree — when the reconciliation logic (reapOrphanedWorkflowRuns) only needs runs with status === "running" (typically 0-5 at any given time).

This is the same load-all-to-find-few pattern that caused the workflow history get OOM fixed in #2097.

Reproduction

A repo with 415 workflow runs (~22 MB on disk, ~400 dataArtifacts per run) OOMs every boot:

2026-08-07T22:30:12.962Z [INF] serve: Policy snapshot loaded (reload mode: "manual")
2026-08-07T22:30:12.963Z [INF] serve: Boot: reaping stale runs via tracker
2026-08-07T22:30:12.976Z [INF] serve: Boot: reconciling workflow run state
[7:0x56546e704000]    86125 ms: Incremental Mark-Compact (reduce) 4088.1 (4093.9) -> 4087.8 (4091.2) MB
#
# Fatal JavaScript out of memory: Ineffective mark-compacts near heap limit

Root Cause

findAllGlobalSince (yaml_workflow_run_repository.ts:342) iterates every workflow directory, reads every YAML file, YAML-parses each, runs full Zod validation via WorkflowRun.fromData(), and collects all results into a single array. With 415 runs x ~400 data artifacts each, this materializes ~166,000 DataArtifactRef objects plus all jobs/steps/outputs.

The boot reconciliation only touches runs where run.status === "running", checking them against the SQLite tracker and PID liveness. It reads run.id, run.status, run.pid, run.instanceId, and run.workflowName. Full aggregate hydration is only needed for the handful of running runs that need run.interrupt() + save().

A secondary instance of the same pattern exists in the graceful shutdown path at serve.ts:3000, which calls findAllGlobalSince despite already having the specific run IDs it needs.

Proposed Fix

  1. Add a method that uses the per-workflow runs index (.runs-index.json, added in #2097) to identify runs with status: "running", then loads only those as full aggregates via findById. Falls back to a lightweight summary scan when no index exists.
  2. Update boot reconciliation to use the new method instead of findAllGlobalSince.
  3. Update the graceful shutdown path to use targeted findById lookups since it already has the run IDs.

This turns O(all_runs) full-aggregate parse into O(running_runs) full-aggregate parse + O(all_indexed) JSON index read.

  • #2097 — fixed the same pattern in findLatestByWorkflowId
  • #1552 — original report covering both OOMs
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 4 MOREREVIEW+ 4 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

8/8/2026, 12:21:49 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack728/7/2026, 10:54:26 PM

Sign in to post a ripple.