Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneeszack

Relationships

#3779 Dashboard Workflows page takes ~19s on a serve with many extension workflows: workflow.search resolves each workflow's owner with a full extension-workflow scan

Opened by zack · 10/22/2026· Shipped 10/22/2026

Summary

The dashboard Workflows page takes ~19s to load on a serve whose workflows come mostly from pulled extensions. The time is spent in workflow.search's authorization filter: it resolves each listed workflow's owner with a separate repository lookup, and every lookup of an extension workflow re-walks and re-parses every pulled extension's YAML. The cost is quadratic in the number of extension workflows.

Observed (ops.swamp-club.com, serve bfde0792)

Timed over the serve WebSocket with a user token:

Request Alone Sent together, as the page does
workflow.search (141 workflows) 13–15s 19s
workflow.run.summary (since: 7d, recentLimit: 20) 2s 7s

The page waits on both. The run summary slows when sent together because search's synchronous YAML parsing blocks the event loop. The repo has 29 workflows in workflows/; the other ~112 come from pulled extensions.

Cause

handleWorkflowSearch (src/serve/handlers/workflow_handlers.ts) lists every workflow, then runs filterByResources with canonical.workflowOwners(item.id, item.name) for each one. That path is recordedWorkflowOwners → resolveRecordedWorkflow → findWorkflowById(repo, id, name) → CompositeWorkflowRepository.findByName(name):

  • A workflow in workflows/ is found by YamlWorkflowRepository.findByName from its cached listings. This is cheap.
  • An extension workflow misses the primary repository and reaches ExtensionWorkflowRepository.findByName, which calls findAll(). findAll() walks every extension workflows directory and parses every *.yaml/*.yml in it.

CanonicalResources memoizes per (id, name), but every extension workflow has a distinct key, so one search request does about 112 full extension walks. CanonicalResources already avoids this pattern for model definitions with a single per-request #index ("a scan per id would make a collection of N items cost N full scans"). Workflows have no equivalent.

Reproduction (in-process)

A temp repo built from a real workflow YAML template, with file mtimes backdated so YamlWorkflowRepository listings are cached as in production. Times are for CanonicalResources.workflowOwners over every workflow from repo.findAll(), which is what one workflow.search request does:

Primary workflows Extension workflows workflowOwners total
141 0 239ms
29 112 1,624ms (1,593ms in extension lookups; one extension findAll = 12ms)
29 400 18,530ms

On the ops serve one extension walk costs about 100ms (larger pulled-extension trees), which gives the observed 13s+.

Proposed fix

Read once per request: CanonicalResources builds a single per-request workflow index on first use, the same way it already does for definitions, and workflowOwners resolves from that index instead of issuing a repository lookup per workflow. The index must keep resolveRecordedWorkflow's current semantics exactly:

  • Prefer the workflow with both the recorded name and id. A primary workflow shadows an extension workflow of the same name.
  • Otherwise fall back to lookup by id alone (CompositeWorkflowRepository.findById order: primary first, then extension).
  • Otherwise the workflow is "missing" and is judged on its recorded name.
  • A read failure still fails the request.

A request then costs one primary listing plus one extension walk, however many workflows it authorizes. Add a regression test that counts extension findAll calls (one per request, not one per workflow) rather than asserting on wall-clock time.

Main (983e2929) has the same behavior; the only change to this path since bfde0792 is swamp-club#3741's tracing.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 9 MOREPR_LINKED+ 2 MORESESSION_SUMMARIZED

Shipped

10/22/2026, 3:39:36 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
zack assigned zack10/22/2026, 2:24:40 PM

Sign in to post a ripple.