Background sessions (claude agents) drop work-phase OTEL logs (user_prompt/api_request) on shutdown

Status Open
Reported on v2.1.159
Maintainer reply None cached
Activity 6 comments · opened Jun 1, 2026

Summary

Background sessions dispatched via claude agents do not export their work-phase OpenTelemetry logs (user_prompt, api_request, tool_result, tool_decision) to the configured OTLP endpoint. Only the early bootstrap events (plugin_loaded, hook_registered, mcp_server_connection, hook_execution_complete) arrive. Interactive sessions and claude -p runs export everything correctly.

The user-visible effect: a productivity/observability dashboard built on Claude Code telemetry shows no prompts and no API activity for background-agent sessions, even though those sessions run to completion and clearly make API calls.

Environment

  • Claude Code: 2.1.159
  • OS: macOS (Darwin 25.5.0), Apple Silicon
  • Telemetry: CLAUDE_CODE_ENABLE_TELEMETRY=1, OTLP exporter over gRPC to a self-hosted collector (OpenTelemetry Collector → VictoriaLogs). OTEL_LOGS_EXPORTER=otlp, OTEL_METRICS_EXPORTER=otlp.

Expected behavior

A background session dispatched via claude agents should emit the same log events as an interactive or -p session — in particular claude_code.user_prompt for the dispatched task and claude_code.api_request for each model call.

Actual behavior

The dispatched worker session emits only bootstrap events. The work-phase events never reach the OTLP endpoint.

Reproduction

  1. Configure OTLP telemetry in ~/.claude/settings.json (CLAUDE_CODE_ENABLE_TELEMETRY=1 + OTLP exporter to a collector you can query).
  2. Run claude agents, type a trivial task (e.g. Run: echo HELLO-<unique-nonce>, then stop), press Enter to dispatch a background session.
  3. Wait for the session to reach Completed in the agents view (its output is correct — it ran).
  4. Query the collector/log backend for that session.

Observed in the log backend for the dispatched session:

event.name           count
-------------------  -----
plugin_loaded         15
hook_registered        3
mcp_server_connection  2
hook_execution_complete 1
user_prompt            0   <-- missing
api_request            0   <-- missing
tool_result            0   <-- missing
tool_decision          0   <-- missing

The unique nonce from the task prompt is nowhere in the backend. A normal claude -p "<same task>" run in the same environment, by contrast, produces user_prompt (with the prompt body when OTEL_LOG_USER_PROMPTS=1), api_request, and tool_result as expected.

The two dispatched worker sessions in our run were created at the dispatch moment and contained only the bootstrap event set above.

Likely root cause (hypothesis)

The bootstrap events are emitted during session init and make it into an early log batch that flushes. The work-phase events (user_prompt, api_request, …) are emitted afterward and appear to be lost when the supervisor terminates the detached worker — i.e., the OTLP log batch processor is not force-flushed on background-session shutdown. Metrics and the initial bootstrap batch survive; the pending work-phase log batch does not.

This is consistent with:

  • claude -p (graceful foreground exit) exporting everything, including user_prompt + body.
  • Interactive sessions (long-lived) exporting everything.
  • Only claude agents detached/supervised workers (short-lived, terminated by the supervisor) losing the work-phase logs.

It does not appear to be a startup race (the work events are emitted well after the event logger initializes) nor a redaction issue (the events are entirely absent, not <REDACTED>).

Impact

Any observability/cost/productivity tooling built on Claude Code OTLP logs under-reports or entirely misses background-agent activity. Background agents are increasingly used for autonomous/fleet workflows, so their telemetry being invisible is a meaningful gap.

Suggested fix

Force-flush (or graceful-shutdown) the OTLP log provider when a background/claude agents worker session terminates, the same way a -p/interactive session does on clean exit. A SIGTERM handler that calls the logger provider's forceFlush()/shutdown() before exit would likely resolve it.

View original on GitHub ↗

5 Comments

abhinas90 · 3 months ago

Excellent catch — this is a real observability blind spot for anyone running Claude Code agents in background mode for CI/CD or multi-agent orchestration.

The specific pattern you've identified (bootstrap events arrive, work-phase events don't) strongly suggests the OTLP exporter is being shut down before it can flush the work-phase spans. Background sessions in claude agents mode may have a shorter or different teardown path than interactive or -p sessions.

Diagnostic matrix to confirm before Anthropic patches:

  1. Add explicit flush timing — in the background session, check whether OTEL_EXPORTER_OTLP_TIMEOUT and OTEL_BSP_SCHEDULE_DELAY are high enough (default 10000ms and 5000ms respectively). If the session completes faster than the batch export window, spans are dropped.

``
export OTEL_BSP_SCHEDULE_DELAY=1000
export OTEL_BSP_EXPORT_TIMEOUT=15000
``

  1. Force a synchronous flush — if you control the agent wrapper, call opentelemetry.sdk.trace.export.BatchSpanProcessor.force_flush() before process exit. This confirms whether it's a timing issue vs. a configuration issue.
  2. Check OTEL_TRACES_EXPORTER — ensure it's set to otlp in the background agent's environment (not just the interactive session's). Some agent launchers inherit only partial env.
  3. Verify the exporter endpoint — background sessions may be routed through a different network path. A quick curl -v $OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces from inside the agent context catches connectivity issues.

Workaround that's working for us: wrap the background agent in a small shell script that explicitly sets all OTEL vars and adds a sleep 3 before exit to give the batch exporter time to flush. Not elegant, but it keeps the dashboard populated until the root cause is fixed.

The fact that interactive sessions work perfectly confirms the OTLP config itself is correct — this is specifically about the background session lifecycle. Good find.

pghoya2956 · 3 months ago

Thanks! Agreed it's a flush-on-shutdown issue, with two clarifications:

It's the logs signal, not traces. user_prompt / api_request / tool_result are emitted as OTLP log records (we observe them in VictoriaLogs with event.name=...), not spans. So OTEL_BSP_SCHEDULE_DELAY, BatchSpanProcessor.force_flush(), and OTEL_TRACES_EXPORTER don't apply here — the relevant knob would be the log batch processor (OTEL_LOGS_EXPORT_INTERVAL / BLRP), not the span one.

Connectivity / config / env are already confirmed good for the worker. The bootstrap log batch (plugin_loaded, hook_registered, mcp_server_connection, …) reaches the collector from the very same worker session. Only the later work-phase batch is missing — which points squarely at the log provider not being force-flushed when the worker is torn down, rather than at a routing/env problem.

The shell-wrapper + sleep workaround can't apply to claude agents. Those workers are spawned and terminated by the supervisor, not wrapped by the user, so there's no place to inject env vars or a pre-exit sleep. That's exactly why the fix needs to live in Claude Code's background-session shutdown path (force-flush the LogRecordProcessor on SIGTERM/teardown) rather than in user env or wrappers.

Juriy · 2 months ago

Seeing a related and possibly broader effects on macOS with the Claude Code CLI (v2.1.162, Darwin 25.5.0), exporting OTLP http/protobuf to a local grafana/otel-lgtm collector.

The work-phase telemetry loss isn't limited to dispatched workers dropping logs on shutdown. A live, foreground session goes completely dark the moment it enters the Agent view, while still running.

Timeline from one controlled run:

  1. Fresh terminal session, telemetry configured via settings.json env (CLAUDE_CODE_ENABLE_TELEMETRY=1 + OTLP exporter). Single-agent mode emit everything correctly.
  2. Switched the same session into the Agent view.
  3. From that instant: zero events of any kind. The session kept working actively for 7+ minutes afterward, the collector's own otelcol_exporter_sent_log_records_total confirmed 0 records sent for that entire window (so nothing was emitted, not merely dropped downstream).

This doesn't fit the "missing forceFlush() on worker shutdown" hypothesis: the session was alive and emitting normally beforehand, then stopped instantly on the mode transition and stayed silent while running. It looks more like the OTLP exporter is torn down / not re-initialized when a session is re-hosted into the multi-agent runtime, rather than a pending batch lost at exit. settings.json env was present and working right up to the switch, so it isn't the subprocess-env-inheritance gap (#56153) either.

Necmttn · 2 months ago

Given the bootstrap logs arrive, the regression should be signal/lifecycle-specific.

Background worker emits work-phase log records after Agent view dispatch, then shutdown force-flushes logs before process teardown. I would test logs, not spans: one foreground run, one claude -p run, one background agent, the same OTLP collector, a unique nonce in user_prompt, and assertions that api_request/tool_result records arrive for all three. Include a short task that finishes before the batch interval.

---

_Generated with ax._

cafe24-jclee03 · 1 month ago

Seeing the same thing, but on plain interactive sessions — not background agents. Might be worth widening the scope here.

Setup: Claude Code 2.1.201 on macOS, OTLP/HTTP to a self-hosted collector, with OTEL_LOG_USER_PROMPTS=1 and OTEL_LOG_TOOL_DETAILS=1.

In a normal claude session (no -p, no agents), user_prompt stops showing up after the first prompt or two, while everything else keeps flowing fine for the rest of the session — api_request, assistant_response, tool_result, hook_*, etc. So it doesn't look like a shutdown-flush issue in our case: the session is still alive and still emitting other events, but user_prompt alone goes quiet.

I diffed the local transcript (~/.claude/projects/.../<session>.jsonl, which has every prompt) against what actually landed in the backend. Across three sessions: a resumed one lost all of them, a fresh one typed 8 prompts and only 2 arrived (the 1st and 3rd), and another fresh one lost all 5. In the 8-prompt session the backend keeps getting api_request/assistant_response events long after user_prompt stopped at prompt 3, and same-second sibling events from the later prompts do get indexed — so it's not indexing lag or a dropped connection. Pulling the raw event for a missing prompt shows the prompt attribute isn't there at all, so it reads like the event was never created rather than dropped in transit.

I also tried OTEL_BLRP_SCHEDULE_DELAY=1000 (5s→1s) thinking it was a batch flush thing. No difference — that session sent 37 events fine and still 0 user_prompt. Since the events that do arrive go through the same batch processor, I don't think it's a flush bug; feels more like the later user_prompt records just aren't being generated.

Would be good to check the fix against long-lived interactive sessions too, not just background shutdown. Can share sanitized diffs if that helps.

Showing cached comments. Read the full discussion on GitHub ↗