docs(traces): refine context propagation and attribute limits - #56
Merged
Conversation
Records three corrections the first traces port surfaced: pass-through inert handles, inbound sampled-flag propagation, and a bound on attribute value length with exception.stacktrace.
turnipdabeets
marked this pull request as ready for review
September 1, 2026 19:29
dustinbyrne
reviewed
Sep 1, 2026
dustinbyrne
left a comment
Contributor
There was a problem hiding this comment.
i don't see a spec.md under openspec/specs, is this missing a final step?
Merges the six modified requirements into the canonical traces spec and moves the change into the archive, matching how every other change in this repo lands.
Collaborator
Author
|
Good catch — it was. Fixed in d33c924. |
dustinbyrne
approved these changes
Sep 1, 2026
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.
Why
The traces capability was specified before any SDK implemented it. Reviewing the first port —
posthog-node, PostHog/posthog-js#4579 and the PRs stacked on it — surfaced three places where the v1 text assumes something the implementation found to be wrong or incomplete. All three came out of @jonmcwest's review. Each is a cross-SDK contract, so each belongs here rather than in one port.posthog-nodeimplements all three today. This makes the canonical text match, rather than leaving the reference implementation silently divergent.What changes
openspec/changes/refine-traces-propagation-and-limits/— six MODIFIED requirements in thetracescapability.1. Pass-through inert handles (
No-op span handles)The spec says a no-op handle SHALL never be activated and SHALL never produce a well-formed
traceparent. Right for a handle with nothing behind it — but it also silences a service in the middle of a traced chain that simply hasn't enabledtracesyet: it forwards no header, so every downstream hop starts a fresh trace and the chain breaks at the least-instrumented service.OTel makes this the one exception to no-op behavior: with no SDK installed "the API MUST return a non-recording
Spanwith theSpanContextin the parentContext". Echoing an inbound header invents no ids — the span id forwarded is the upstream caller's, and upstream recorded it.So "inert" splits in two. A no-op handle (no inbound context) keeps its current contract exactly. A pass-through handle echoes the inbound header verbatim — including its version byte, since it re-emits a header it didn't construct — and is activated by scoped helpers so
getActiveSpan()?.traceparent()works inside the callback.2. Inbound sampled-flag propagation (
Trace context interop,Span data model)The spec pins
traceparentat-01and the wireflagslow byte at1. Legal under W3C, and truthful while the SDK records everything — but it breaks head-sampled fleets. Service A samples a trace out (00), a PostHog-traced service B propagates01, and a downstream OTel service C with the defaultParentBasedsampler records a trace its own head sampler already rejected.Recording and propagating are separable; OTel already names this combination
RECORD_ONLY. The wireflagsnow carries the same byte the span propagates, so the record and the header can't disagree, and the has-is-remote / is-remote bits get set from parent remoteness — which the SDK always knows, and which can never be backfilled.Also adds the W3C rule that flag bits the emitted version doesn't define are zeroed rather than forwarded.
Confirmed before proposing: ingestion stores
flagsverbatim and reads nothing from it, so a cleared sampled bit changes no server behavior today.3.
maxAttributeValueLengthandexception.stacktrace(Span limits,Configuration knobs,Scoped span helpers and exception recording)The count caps bound how many entries a span carries, not how big one is. A single multi-megabyte value pushes the span past the ingestion body limit; the too-large path then halves the batch down to that span, finds it still too large, and drops it whole. The user loses the span rather than the excess.
The bound has to reach inside arrays and maps —
setAttribute("payload", { body: res.body })is the ordinary way an oversized value arrives. Traversal is depth-limited so a self-referencing value terminates, and a value the SDK can't walk safely is left alone rather than taken as a reason to drop the span.exception.stacktracewas deferred by the add-traces design, which invited exactly this follow-up: "exception.type/exception.messageare the v1 contract, and ports may propose the stacktrace attribute … as a follow-up." The reason to defer was that no scrub hook and no size bound existed yet. Both now do. Ordering is normative: attached beforebeforeSpanSendso a hook can drop it, bounded after so a hook can't reintroduce an unbounded one.Notes for review
tasks.md: shouldmaxAttributeValueLengthcarry one cross-SDK default, or stay per-platform like every other knob in this capability?posthog-nodechose 8192.parentnow forwards the inbound header where it previously forwarded nothing. No span is recorded either way.flagsgains values the server already stores without interpreting, andexception.stacktraceis an additional event attribute.Checks
openspec validate refine-traces-propagation-and-limits --strict— passesopenspec validate --specs --strict— 62 passed, 0 failedgit diff --check— clean🤖 Generated with Claude Code
https://claude.ai/code/session_015Lz2epJDdgSt12vUMNcfDZ