Skip to content

feat: propagate ACP trace context metadata - #910

Open
xiaoziv wants to merge 5 commits into
agentclientprotocol:mainfrom
xiaoziv:feat/acp-trace-context-metadata
Open

feat: propagate ACP trace context metadata#910
xiaoziv wants to merge 5 commits into
agentclientprotocol:mainfrom
xiaoziv:feat/acp-trace-context-metadata

Conversation

@xiaoziv

@xiaoziv xiaoziv commented Jul 24, 2026

Copy link
Copy Markdown

Summary

Add support for propagating W3C trace context from ACP prompt metadata to the Claude Agent SDK environment.

Changes

  • Read string-valued traceparent and tracestate fields from session/prompt.params._meta.
  • Apply them as TRACEPARENT and TRACESTATE before submitting the prompt.
  • Clear or replace stale trace context before subsequent prompts.
  • Preserve existing inline flag-level environment settings.
  • Reject trace context updates while the session is busy to avoid changing the environment of an in-flight prompt.
  • Coordinate prompt submission, cancellation, and settings updates to prevent race conditions.
  • Close the session if applying or restoring trace settings times out or leaves the environment state unknown.
  • Document the supported ACP metadata fields and lifecycle requirements.

baggage is intentionally ignored for now.

Example

{
  "sessionId": "existing-session-id",
  "prompt": [{ "type": "text", "text": "Run the task" }],
  "_meta": {
    "traceparent": "00-80e1afed08e019fc1110464cfa66635c-7a085853722dc6d2-01",
    "tracestate": "vendor=value"
  }
}

Validation

  • TypeScript type checking
  • ESLint
  • Prettier
  • Full test suite: 667 passed, 20 skipped

@xiaoziv
xiaoziv force-pushed the feat/acp-trace-context-metadata branch from bdbdaef to 7a3e057 Compare July 28, 2026 02:32
@xiaoziv

xiaoziv commented Jul 28, 2026

Copy link
Copy Markdown
Author

This PR is now rebased onto the latest main, with all conflicts resolved and the full test suite passing.
A prompt review and merge would be greatly appreciated. @benbrandt

xiaoziv and others added 3 commits August 1, 2026 18:40
Two fixes to the ACP trace-context propagation, both verified against the
real CLI with an OTLP sink.

Clearing never worked. `applyFlagSettings({ env: null })` drops `env` from
the flag settings layer, but the CLI only ever *assigns* settings `env` onto
its live `process.env` — it never unsets a key that disappeared. So an
untraced prompt following a traced one kept the previous prompt's
`TRACEPARENT` and its `claude_code.interaction` span stayed parented under
it, for the rest of the session. Writing the vars explicitly blank clears
them: an empty value reads as absent to the CLI's span-context extraction.
Through the adapter over ACP stdio, turn 3 (untraced, after a traced turn 2)
went from `traceId=1111…, parent=2222…` to a fresh root span.

The unit tests couldn't catch this: they asserted the mock was *called* with
`{env: null}`, which it was.

Prompt admission regressed for every client. Any `session/prompt` arriving
while `query.interrupt()` was in flight — or whose preparation began before a
`cancel()` — returned `{stopReason: "cancelled"}` without ever being pushed
to the SDK, including prompts carrying no `_meta` at all. That is the
ordinary "hit stop, then send a new message" flow, and an interrupt is a
control round-trip that can take a while (hence the 30s force-cancel grace).
The generation check exists to cover the awaited trace-env round-trip, so it
is now consulted only when that round-trip actually happened; the refcounted
cancel barrier is gone, which also restores the plain `await
session.query.interrupt()`.

Also document the telemetry env the feature needs: without
`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA` no trace spans are exported at all and
the metadata has no observable effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the trace-context fixes, based on what the CLI actually does
with `TRACEPARENT`. Verified end-to-end by driving the built adapter over ACP
stdio against an OTLP sink.

Session-level trace context. `session/new`'s `_meta.traceparent` /
`_meta.tracestate` are baked into the query's spawn env, so the whole session
joins the caller's trace with no control round-trip, no race against a queued
prompt, and nothing to clear afterwards. This is what most callers want and
it avoids every failure mode the per-prompt path has to handle. A prompt that
carries its own context still overrides it for that one prompt — and now
reverts to the session's context rather than to nothing.

Queueing works again. The CLI reads `TRACEPARENT` when it *dequeues* a
message, not when the message is enqueued, so a turn the CLI has already
started cannot be pulled into a later prompt's trace. Requiring a fully idle
session was therefore stronger than correctness needs: what matters is that
no previously submitted prompt is still waiting to start. A traced prompt can
now be queued behind the running turn, which is the ordinary "send a
follow-up while the agent works" flow. The two conditions clients could not
act on anyway — `pendingOrphanResults` and `orphanCommands`, internal
cancellation bookkeeping — are gone: an undequeued orphan would consume the
new env, but the prompt behind it still reads the same value, so this prompt
lands where it should either way.

Applying trace context is best effort. A timed-out or failed control
round-trip no longer closes the session or fails the prompt — losing a parent
span means a new root span, not a lost turn. The live env is unknowable after
a failure, so the session is recorded as still carrying prompt context and
the next prompt retries the reset; an extra reset is harmless where a missed
one would leak the trace into later prompts.

That also removes the rollback machinery it existed to serve:
`restoreBaseFlagEnv`, the compensation closure, the preparation-generation
counter, and the `cancel()` hook that bumped it.

`baseFlagEnv` is gone too. It existed to reconstruct a baseline to restore,
but writing only the two trace vars cannot unset env inherited from the
session's inline `settings` or from a lower settings layer — the same
CLI non-deletion behaviour that made the blank-value fix necessary. Verified
with a project-layer + inline-settings + flag-layer probe. With no baseline to
model, the "opaque settings path" rejection goes away: a session may now pass
`settings` as a path and still use trace context.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xiaoziv
xiaoziv force-pushed the feat/acp-trace-context-metadata branch from de6521a to 32f6db1 Compare August 1, 2026 10:42
@xiaoziv

xiaoziv commented Aug 6, 2026

Copy link
Copy Markdown
Author

Hi @benbrandt — a gentle follow-up on this PR. It has been updated against the latest main, and the trace-context behavior has been verified end-to-end against the real Claude CLI with an OTLP sink. The latest CI
workflows are currently waiting for maintainer approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants