A background span is a Client span from a backend service that has no parent: a database query or an HTTP call to another API that was not made on behalf of a request, a job, or a command. OpenTelemetry starts a new trace for each of them, so they show up in the Traces list as if they were entry points.
Root Client spans from a browser service are not background spans: a fetch started by a user action is the normal entry point of a trace that continues in your API. Jobs queued by a scheduler on their own are expected too, and labeled Queue. Background spans are labeled bg in the Entry column, and the Background toggle hides them from the list and the chart. You can also filter on background in the search bar.
Why it matters
A few background spans are expected, for example the queries run once when your app boots. Many of them usually mean the instrumentation loses track of what your code is doing:
- The queries are not attached to the work that ran them, so you cannot see which job or loop is slow or noisy.
- Each one is a separate trace, which floods the Traces list and counts against your usage.
- Errors in these calls have no context: no route, no job, no user.
Common causes
Work that runs outside a request, job, or command
Polling loops, setInterval timers, schedulers, and queue workers that are not instrumented run
without an active span. Every query they make becomes its own trace. Wrap each iteration in a
span with record() so its queries become child spans.
Context lost across an async boundary
Event emitters, callbacks, connection pools, and some promise libraries do not propagate the
OpenTelemetry context. The call still runs, but the active span is gone when it starts. Keep the
work inside the async function that holds the span, or pass the context explicitly with
context.with().
Instrumentation loaded too late
If otel.ts is not the first import of your entry point, libraries load before they are patched
and requests may not create a parent span. See Troubleshooting.
Noise you do not need
Health checks of internal pools or a tight polling query may not be worth tracing at all.
Silence them with suppressTracing() from @opentelemetry/core instead of keeping thousands of
one-span traces.
Investigate with your coding agent
Connect the Monocle MCP to Claude Code, Cursor, or another agent, open a background span in Traces, and click Copy prompt for your agent in its panel. You can also copy the prompt below and replace the trace ID:
My Monocle project has "background spans": Client spans (database queries, HTTP calls) from a
backend service, without a parent, so each one starts its own trace. Example trace ID: <TRACE_ID>.
Root Client spans from a browser service (fetch calls started by a user action) are normal entry
points: ignore them.
Use the Monocle MCP to find out why:
1. Call get_trace on that trace ID, note the span name, service, and attributes.
2. Call list_traces over the last 24 hours with the filter
{"field":"background","operator":"eq","value":"true"} to see how many background traces exist
and which span names repeat most.
3. Find in this codebase where these calls are made, and why no span is active at that point
(polling loop, timer, worker, lost async context, instrumentation loaded too late…).
Propose the smallest fix: wrap the work in a span, restore the context, load the instrumentation
earlier, or suppress tracing if the calls are pure noise. Do not change unrelated code.