Skip to content

fix(langfuse): finalize open root spans at exit so short-lived processes export complete traces - #82332

Closed
aldoeliacim wants to merge 1 commit into
NousResearch:mainfrom
aldoeliacim:fix/langfuse-atexit-root-span-finalize
Closed

fix(langfuse): finalize open root spans at exit so short-lived processes export complete traces#82332
aldoeliacim wants to merge 1 commit into
NousResearch:mainfrom
aldoeliacim:fix/langfuse-atexit-root-span-finalize

Conversation

@aldoeliacim

Copy link
Copy Markdown
Contributor

What

Short-lived Hermes processes (kanban workers, hermes chat -q one-shots, cron jobs) export anonymous traces to Langfuse: no name, no sessionId, empty metadata — just a bag of orphaned observations. On a board running a few parallel workers this quickly becomes the majority of traces (33 of the most recent 50 on the instance where this was diagnosed).

Root cause: the plugin ends the root span in _finish_trace(), which only runs when a turn finalizes (final assistant message with no tool calls). A process that exits while the last LLM response still has tool calls queued never reaches it. The SDK's own atexit flush then exports the ENDED child observations, but the root span never lands — so the backend renders a nameless trace shell around orphaned children.

Fix: _finalize_all_traces() drains _TRACE_STATE and ends every open root span (children first), registered with atexit immediately after the Langfuse client is constructed. atexit is LIFO, so it runs BEFORE the SDK's own shutdown hook and the spans ended here are still picked up by the exporter. Idempotent; fail-open per span; no-op when nothing is pending.

Why

  • Anonymous traces are unusable for debugging: they can't be found by session, carry no model/platform metadata, and pollute every trace listing.
  • Any deployment that runs workers/cron alongside the gateway hits this constantly.

How to test

scripts/run_tests.sh tests/plugins/test_langfuse_plugin.py -- -q

29 tests (2 new): _finalize_all_traces ends every open root and drains state (idempotent on re-entry), and the atexit hook is registered on client init. Both new tests fail without the fix (verified by stashing it).

Live verification on the diagnosed instance: after the fix, a hermes chat -q one-shot exports a complete "Hermes turn" trace with session id; before, the same flow produced a nameless trace with 17+ orphaned observations.

Tested on Linux (aarch64).

…ses export complete traces

Langfuse-driven correctness review, bug 3: kanban workers / hermes chat -q
/ cron one-shots exit while the final LLM response still has tool calls
queued, so _finish_trace never runs. The SDK's own atexit flush exports
the ENDED children but the root span never lands: the backend shows an
anonymous trace — name '', no sessionId, empty metadata — with 17-37
orphaned observations (4 seen live in one kanban tick, 33/50 of recent
traces anonymous).

_finalize_all_traces drains _TRACE_STATE and ends every open root;
registered with atexit AFTER Langfuse client construction (atexit is
LIFO → runs before the SDK's shutdown flush, so the ended spans still
export). Idempotent; fail-open per span.

RED-verified: both new tests fail with the fix stashed; 29/29 green
with it.
@erosika

erosika commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

adopted into #83437 with authorship preserved. one interaction fix on top: the atexit finalizer there also ends subagent observations and exits the root observation's context manager, matching the CM-unwind fix from #81054.

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins telemetry Touches outbound telemetry, usage attribution, or analytics — needs opt-in gating before merge labels Aug 10, 2026
kshitijk4poor added a commit that referenced this pull request Aug 13, 2026
… fan-out

Salvaged from PR #83437 by @erosika, with adopted fixes from @bgodlin (#81054),
@aldoeliacim (#82332), @nftpoetrist (#42326), @rodboev (#39653), @FnExpress
(#64292, supersedes #32175 by @db-aeon), @Per0-1 (#61166), @NaMinhyeok (#64797),
and @liuhao1024 (#43130).

Widens the bundled Langfuse plugin from 6 to 11 hooks and fixes two
attribution bugs. Also adopts shutdown/atexit lifecycle fixes and composes
8 prior community PRs with interaction-fix follow-ups.

Model attribution: on_pre_llm_request and on_post_llm_call now prefer the
wire value (request body model, response model) over the agent attribute,
which goes stale after /model switch or provider fallback.

Cost total: both cost paths now send a summed total alongside the per-type
breakdown, since Langfuse does not derive calculatedTotalCost from
cost_details keys. Subscription-included routes send no cost keys at all.

New coverage: api_request_error closes failed generations with ERROR level;
on_session_finalize/on_session_end close dangling traces for tool-only and
interrupted turns; subagent_start/subagent_stop trace delegated children as
spans; MoA advisor fan-out emits one generation per advisor priced at the
advisor's own model.

Capture modes: HERMES_LANGFUSE_CAPTURE=metadata|sanitized|full (default
sanitized). Sanitized mode redacts secret patterns before truncation.

Adopted lifecycle fixes: shutdown client at session finalize when
reason=shutdown (not on session rotation); atexit finalizer ends open root
spans for short-lived processes; root context manager exited to prevent
interpreter-teardown TypeError; TOCTOU on _get_langfuse() fixed with lock;
reasoning_content surfaced in traces; system prompt included in generation
input for Anthropic/Codex/Bedrock; SDK v3 update_trace replaces set_trace_io.

Closes #29482, #43129, #72661.
Supersedes #81054, #82332, #42326, #39653, #64292, #32175, #61166, #64797, #43130.
Partially addresses #67544 (capture modes + secret redaction; user_id remains open).
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via #85439 — your fix was adopted and composed into the wider Langfuse tracing PR by @erosika. Your contribution is credited in the commit body. Thanks @aldoeliacim!

prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
… fan-out

Salvaged from PR NousResearch#83437 by @erosika, with adopted fixes from @bgodlin (NousResearch#81054),
@aldoeliacim (NousResearch#82332), @nftpoetrist (NousResearch#42326), @rodboev (NousResearch#39653), @FnExpress
(NousResearch#64292, supersedes NousResearch#32175 by @db-aeon), @Per0-1 (NousResearch#61166), @NaMinhyeok (NousResearch#64797),
and @liuhao1024 (NousResearch#43130).

Widens the bundled Langfuse plugin from 6 to 11 hooks and fixes two
attribution bugs. Also adopts shutdown/atexit lifecycle fixes and composes
8 prior community PRs with interaction-fix follow-ups.

Model attribution: on_pre_llm_request and on_post_llm_call now prefer the
wire value (request body model, response model) over the agent attribute,
which goes stale after /model switch or provider fallback.

Cost total: both cost paths now send a summed total alongside the per-type
breakdown, since Langfuse does not derive calculatedTotalCost from
cost_details keys. Subscription-included routes send no cost keys at all.

New coverage: api_request_error closes failed generations with ERROR level;
on_session_finalize/on_session_end close dangling traces for tool-only and
interrupted turns; subagent_start/subagent_stop trace delegated children as
spans; MoA advisor fan-out emits one generation per advisor priced at the
advisor's own model.

Capture modes: HERMES_LANGFUSE_CAPTURE=metadata|sanitized|full (default
sanitized). Sanitized mode redacts secret patterns before truncation.

Adopted lifecycle fixes: shutdown client at session finalize when
reason=shutdown (not on session rotation); atexit finalizer ends open root
spans for short-lived processes; root context manager exited to prevent
interpreter-teardown TypeError; TOCTOU on _get_langfuse() fixed with lock;
reasoning_content surfaced in traces; system prompt included in generation
input for Anthropic/Codex/Bedrock; SDK v3 update_trace replaces set_trace_io.

Closes NousResearch#29482, NousResearch#43129, NousResearch#72661.
Supersedes NousResearch#81054, NousResearch#82332, NousResearch#42326, NousResearch#39653, NousResearch#64292, NousResearch#32175, NousResearch#61166, NousResearch#64797, NousResearch#43130.
Partially addresses NousResearch#67544 (capture modes + secret redaction; user_id remains open).
bobaba76 pushed a commit to bobaba76/hermes-agent that referenced this pull request Aug 27, 2026
… fan-out

Salvaged from PR NousResearch#83437 by @erosika, with adopted fixes from @bgodlin (NousResearch#81054),
@aldoeliacim (NousResearch#82332), @nftpoetrist (NousResearch#42326), @rodboev (NousResearch#39653), @FnExpress
(NousResearch#64292, supersedes NousResearch#32175 by @db-aeon), @Per0-1 (NousResearch#61166), @NaMinhyeok (NousResearch#64797),
and @liuhao1024 (NousResearch#43130).

Widens the bundled Langfuse plugin from 6 to 11 hooks and fixes two
attribution bugs. Also adopts shutdown/atexit lifecycle fixes and composes
8 prior community PRs with interaction-fix follow-ups.

Model attribution: on_pre_llm_request and on_post_llm_call now prefer the
wire value (request body model, response model) over the agent attribute,
which goes stale after /model switch or provider fallback.

Cost total: both cost paths now send a summed total alongside the per-type
breakdown, since Langfuse does not derive calculatedTotalCost from
cost_details keys. Subscription-included routes send no cost keys at all.

New coverage: api_request_error closes failed generations with ERROR level;
on_session_finalize/on_session_end close dangling traces for tool-only and
interrupted turns; subagent_start/subagent_stop trace delegated children as
spans; MoA advisor fan-out emits one generation per advisor priced at the
advisor's own model.

Capture modes: HERMES_LANGFUSE_CAPTURE=metadata|sanitized|full (default
sanitized). Sanitized mode redacts secret patterns before truncation.

Adopted lifecycle fixes: shutdown client at session finalize when
reason=shutdown (not on session rotation); atexit finalizer ends open root
spans for short-lived processes; root context manager exited to prevent
interpreter-teardown TypeError; TOCTOU on _get_langfuse() fixed with lock;
reasoning_content surfaced in traces; system prompt included in generation
input for Anthropic/Codex/Bedrock; SDK v3 update_trace replaces set_trace_io.

Closes NousResearch#29482, NousResearch#43129, NousResearch#72661.
Supersedes NousResearch#81054, NousResearch#82332, NousResearch#42326, NousResearch#39653, NousResearch#64292, NousResearch#32175, NousResearch#61166, NousResearch#64797, NousResearch#43130.
Partially addresses NousResearch#67544 (capture modes + secret redaction; user_id remains open).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have telemetry Touches outbound telemetry, usage attribution, or analytics — needs opt-in gating before merge type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants