Relationships
#1591 swamp serve is telemetry-dark: scheduled workflow runs never reach /ingest, and the daemon never flushes
Opened by keeb · 8/11/2026· Shipped 8/11/2026
Summary
A long-lived swamp serve is telemetry-dark for its entire uptime. Workflow runs
it executes — scheduled, webhook, and API-triggered — record no cli_invocation
telemetry at all, and the daemon has no flush path of its own. swamp-club
therefore receives zero events for work that demonstrably happened, which is what
reset kneel's 32-day streak in #1590.
This is not a scoring or streak bug. swamp-club cannot credit a day it received no event for. The gap is entirely CLI-side.
Impact
- Scheduled runs contribute nothing to score, streak, or the combat log.
- An operative whose automation is the point of their setup looks idle.
- The more correctly you run swamp (persistent serve, cron triggers), the less of your work is visible. #1590 is the first report; it will not be the last.
Root cause — three independent layers, all in serve
1. Serve never wires the workflow telemetry sink.
WorkflowRunDeps.telemetrySink is optional (src/libswamp/workflows/run.ts:287)
and is bound in exactly one place: src/cli/commands/workflow_run.ts:323, the
interactive swamp workflow run command. Serve's createWorkflowRunDeps
(src/serve/deps.ts, returns at :281) never sets it, and telemetrySink appears
nowhere in src/serve/. Every serve-side run goes through
executeWorkflowWithLocks (src/serve/deps.ts:293) with the sink undefined, so
the run produces no parent entry and no child method-invocation entries. There is
nothing to flush, authed or not.
2. Recording and flushing exist only in the CLI wrapper.
TelemetryService is constructed at src/cli/mod.ts:1233, recordSuccess is
called at :1602, and flushTelemetry at :1613 — all after cli.parse(args)
returns. For swamp serve that instant is process exit. A daemon that never
restarts never flushes, even for whatever it did spool.
3. The only record of a scheduled fire is an OTel span.
ScheduledExecutionService.executeWorkflow wraps each fire in
withSpan("swamp.scheduled.fire", …) (src/libswamp/workflows/scheduled_execution.ts:441).
That goes to the operator's own OTLP endpoint, not to swamp-club — the two
pipelines are entirely separate (HttpTelemetrySender POSTs to
<endpoint>/ingest, src/infrastructure/telemetry/http_telemetry_sender.ts:73).
So the operator watches their workflow fire every 4h in their own Grafana while
swamp-club sees silence, which makes the bug read as "the site lost my streak."
Evidence (from #1590, verified against the prod events mirror)
- 11 consecutive successful scheduled runs, 2026-08-09 04:00 → 2026-08-11 00:00 UTC, each on the 4-hour boundary. Zero events reached swamp-club.
- The reporter's own
serveevents carry whole-processdurationMs(81s, 166s, 330s, 806s) withstartedAt→completedAt— one event per process, emitted at exit. His last one is 2026-08-09 05:06, before the systemd unit went persistent. - Across 230M events,
cli_invocation.source∈{user, agent_skill}only — there is no scheduler or serve source, and no scheduled-run/workflow-run event type in the catalog. - Result: one missing UTC day (last event 2026-08-09 22:41:47Z, next 2026-08-11 01:49:10Z), streak 32 → 1.
Not the cause
- Not OTEL env vars.
OTEL_EXPORTER_OTLP_ENDPOINTand friends only affectwithSpan/OTLP export undersrc/infrastructure/tracing/. They cannot divert or suppress the/ingestsender. - Not auth. The flush attaches
x-api-keyonly when~/.config/swamp/auth.jsonhas credentials (src/cli/mod.ts:1200-1211), and the token is optional — a missing one changes attribution, not whether the send happens.--auth-mode nonegoverns serve's inbound HTTP auth, not outbound telemetry credentials. - Not the cron cadence.
0 */4 * * *fires at 00:00 UTC every day; it could never skip a UTC day. Evenness isn't the problem — silence is.
Suggested fix
Two parts, and the second is the one that's easy to forget:
- Wire a
telemetrySinkinto serve'screateWorkflowRunDepsso scheduled, webhook, and API-triggered runs record parent + child invocations, with a source that distinguishes them from interactive use (the currentcli_invocation.sourceenum has no value for this). - Give the daemon a periodic flush loop. Serve cannot rely on process exit, so
a sink alone just spools forever — and the 7-day hard retention in
TelemetryService.cleanupOldTelemetrywould eventually drop it.
Identity for the flush should come from the same place the CLI gets it: the
user-level identity plus ~/.config/swamp/auth.json credentials the serve
process already runs under.
Environment
- swamp 20260804.201713.0-sha.c582ab91
- Self-hosted
swamp serveas a persistent systemd user service,--auth-mode noneon loopback - Workflow with
trigger.schedule: "0 */4 * * *";/healthreportsscheduling.enabled: true
Related
- #1590 — the originating report (scheduled runs don't count toward the streak).
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.