Conversation
…turn Signed-off-by: David Haifley <david.haifley@solo.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Claude Harness turn against the real Anthropic API exports no
claude_code.*spans under the turn's trace, so the trace holds only the harness runtime's spans (#2922). This holds each prompt until Claude Code can trace it, which makes Claude's spans arrive on every turn.Closes #2922
Why
Claude Code starts a print-mode turn without waiting for its own OpenTelemetry initialization. With an Anthropic API key and no custom base URL, it first waits on a fetch of the organization's managed settings from
api.anthropic.com/api/claude_code/settings. Until the tracer provider and W3C propagator register, every span is a no-op span andTRACEPARENTis ignored. In an actor the turn starts first, soclaude_code.interactionis lost and laterllm_requestandtoolspans become roots of new traces. With Claude Code's debug log routed out of the actor, the first turn logged[engine] turn 1 start16 ms beforeisTelemetryEnabled, and the second 110 ms before the settings fetch settled. The e2e passes because its mock setsANTHROPIC_BASE_URL, which skips the fetch. It is not a flush problem. The driver already waits for the process to exit, and Claude Code flushes what it recorded on exit. Claude Code 2.1.260 and 2.1.282 behave the same, and neither has a setting that makes print mode wait.What changes
--input-format stream-jsoninstead of as an argument, and closes stdin on the terminal result so Claude exits as before.prometheusmetrics reader on a free loopback port. It writes the prompt onceclaude_code.session.countappears there. Claude increments that counter only afterinitializeTelemetryhas registered the tracer provider and propagator, so the counter proves the turn will be traced. Both are documented Claude Code telemetry surfaces.OTEL_EXPORTER_PROMETHEUS_HOSTandOTEL_EXPORTER_PROMETHEUS_PORTjoin the Claude-owned environment, so a Harness cannot redirect the reader.The gate costs about 100 ms on a turn whose settings fetch is still in flight, and next to nothing when it has settled. Bedrock, Vertex and custom base URLs skip the fetch, so their gate opens as soon as initialization finishes, which also closes the smaller race they had.
Verification
v1.0.0-alpha2and the real Anthropic API, 10 of 10 turns exported Claude's spans under the turn's trace. They were cold and warm unary turns, a Bash tool turn (chat,execute_tool,chat) and aSendStreamingMessageturn. The publishedclaude-harnessimage on the same cluster exported none in 7 of 7 turns, 3 of them run alongside.api.anthropic.comexported nothing in 2 of 2 warm runs with stream-json input alone. With the gate it exported both runs' spans, and the turn started 47 to 50 ms after initialization.TestProcessDriverAwaitsTracingruns the test binary as a Claude Code that registers tracing 200 ms after start, and asserts the prompt arrives afterwards. It fails with the gate off.TestTracingGateWait,TestTracingGateReadyReportsUnreadableScrape,TestTracingGateEnvironmentand the updatedTestProcessDriverArgumentsAndStreamcover the bound, early exit, a scrape that cannot be read, the metrics environment and the stdin message.go test ./harness/... ./core/internal/translator/...,go vet,gofmtandgo fixare clean, and so is golangci-lint over./harness/claude/...with the repository config minus its kube-api-linter plugin, which does not load on macOS and checks only API types.