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

Relationships

#1121 flaky test: manual_approval suspension test leaks 3 timers (OTel InMemorySpanExporter setTimeout)

Opened by keeb · 7/13/2026· Shipped 7/13/2026

Summary

The unit test manual_approval suspension does not mark job span as ERROR in src/domain/workflows/execution_service_test.ts fails on main with Deno's resource-leak sanitizer:

Leaks detected:
  - 3 timers were started in this test, but never completed. This is often
    caused by not calling `clearTimeout`.

This reproduces on a pristine checkout (no local changes) and is unrelated to any product code — it is a test-harness leak. Discovered while running the suite during #1118.

Root cause

The test wires up OpenTelemetry with an InMemorySpanExporter + SimpleSpanProcessor. --trace-leaks points at the exporter:

at setTimeout (node:timers:11:10)
at InMemorySpanExporter.export
    (@opentelemetry/sdk-trace-base/1.30.1/.../export/InMemorySpanExporter.js:41:9)
at @opentelemetry/core/.../internal/exporter.js:29:28

InMemorySpanExporter.export schedules the export result callback via setTimeout. The test exports 3 spans, so 3 timers are queued; provider.shutdown() in the finally does not flush/clear these pending timers before the test ends, so Deno's sanitizer reports them as leaked.

Impact

  • The test fails deterministically under deno run test (leak sanitizer on), turning the suite red independent of the change under test — noise that masks real regressions in CI and local runs.

Steps to reproduce

deno test --allow-read --allow-write --allow-env --allow-run --allow-net \
  --allow-sys \
  --filter "manual_approval suspension does not mark job span as ERROR" \
  src/domain/workflows/execution_service_test.ts

Result: FAILED with "3 timers were started in this test, but never completed".

Suggested fix

Options (test-only; no product code change):

  • Await a flush of the span processor/exporter before provider.shutdown() so the exporter's setTimeout callbacks complete (e.g. await provider.forceFlush() then await provider.shutdown()), or
  • Scope the leak sanitizer for this test where the third-party timer is outside our control (Deno.test({ sanitizeOps: false, ... })), with a comment pointing at the OTel InMemorySpanExporter.export setTimeout, or
  • Replace SimpleSpanProcessor + InMemorySpanExporter with a processor/exporter that resolves synchronously in tests.

Prefer the flush approach so the sanitizer stays enabled.

Environment

  • Deno test runner, @opentelemetry/sdk-trace-base 1.30.1.
  • Related: found during #1118 (unrelated FD-leak fix).
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 4 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

7/13/2026, 9:50:15 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
keeb assigned keeb7/13/2026, 9:16:27 PM

Sign in to post a ripple.