Skip to content

feat(analytics): cross-tier span propagation (client fetch header + server withSpan({ request })) - #1726

Open
mantrakp04 wants to merge 32 commits into
feat/custom-events-spans-serverlessfrom
feat/custom-events-spans-propagation
Open

feat(analytics): cross-tier span propagation (client fetch header + server withSpan({ request }))#1726
mantrakp04 wants to merge 32 commits into
feat/custom-events-spans-serverlessfrom
feat/custom-events-spans-propagation

Conversation

@mantrakp04

@mantrakp04 mantrakp04 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1719 (feat/custom-events-spans-serverless).

What

Auto-links a customer's backend span to the caller's client session with zero glue. When a browser calls the customer's own backend, a server span opened with serverApp.withSpan(type, { request }, fn) parents under that tab's $refresh-token → $session-replay → $session-replay-segment chain — the same tree browser events already attach to.

// client: nothing to do — analytics on ⇒ same-origin fetches carry the header

// server: only where you want a span
export const POST = (req: Request) =>
  stackServerApp.withSpan("checkout", { request: req }, async (span) => {
    span.trackEvent("checkout.validated");   // parent_span_ids = [rti-…, sri-…, srsi-…]
  });

How

One header, x-hexclave-span-context: v1.<base64url-json>, carrying raw ids ({ projectId, sessionReplaySegmentId?, sessionReplayId?, customParentSpanIds? }). The client knows only the segment id; the server resolves rti- from the session; the backend derives sri- and composes all the rti-/sri-/srsi-/cs- prefixes. Nothing client-side ever fabricates a system id.

  • span-propagation.ts — versioned header codec, a same-origin fetch auto-wrapper (idempotent via a global marker, preserves native header precedence then adds ours, skips no-cors, restores the exact original on uninstall), and the origin predicate.
  • server-request-context.ts — AsyncLocalStorage ambient request context, so everything inside a withSpan({ request }) callback inherits it (even bare serverApp.trackEvent).
  • server-app-implwithSpan/trackEvent({ request }) resolve the context; the server-telemetry buffer is re-keyed from userId → the full (user, refreshToken, replay, segment) context so two requests never share a batch's ancestry.
  • backend events/batch — accepts the forwarded refresh_token_id/session_replay_id on server auth (the secret key is trusted), rejects them on client auth, composes [rti-, sri-, srsi-, cs-], and stamps the scalar columns (not just parent_span_ids).
  • client-app-impl — installs the wrapper on init behind analytics.spanPropagation (same-origin default + exact-origin targets allowlist); reads the live per-tab id so it tracks sign-out rotation.

Trust / safety

Best-effort telemetry, never authz. Only rti-/userId/project/branch are server-trusted; sri-/srsi-/custom parents are untrusted labels from a client-controlled header. Header is same-origin only by default (cross-origin would leak context + trip CORS preflight), version/size/uuid/project validated, and any bad header is ignored — never thrown into the request.

Design validated with Codex (gpt-5.5 xhigh): versioned opaque header (deliberately not traceparent, to avoid clobbering the customer's own OTel), Date.now()-free RMT semantics, and the same-origin propagation default.

Tests

  • 24 unit tests (span-propagation.test.ts): codec round-trip / rejection / caps, same-origin predicate, and the fetch wrapper's edge cases (Request vs string vs URL input, init-headers precedence, no-cors, idempotency, clean uninstall).
  • 128 existing template tests still green (server-buffer refactor caused no regression).
  • 2 e2e (analytics-events-batch.test.ts): a server-auth batch with forwarded context → CH event carries [rti-,sri-,srsi-,cs-] + stamped scalar columns; client auth rejects the forwarded context.
  • Typecheck green across backend, template, and all four generated SDKs (js/react/next/tanstack-start); changed-file eslint clean.

Follow-up (separate stack)

Framework adapters — tRPC, Convex, oRPC, ElysiaJS — build on this primitive so consumers never pass { request } themselves (the adapter pulls it from the framework's context). They're a full auth+telemetry integration, shipping on their own stacked branch.


Summary by cubic

Links backend spans and events to the caller’s browser session and trace using same-origin traceparent + x-hexclave-span-context headers and request-aware server APIs. The propagation codec lives in @hexclave/shared for strict, stable parsing across SDKs and backend.

  • New Features

    • Client: auto-adds traceparent and x-hexclave-span-context on same-origin fetch (allowlist via analytics.spanPropagation.targets, skips no-cors), and never overwrites an explicit header.
    • Client APIs: getSpanPropagationHeaders({ parentIds?, root? }) for non-fetch; span handle kit (span.withSpan, span.run, span.getPropagationHeaders, span.fetch) with exact-only parenting via prologue-open sync frames.
    • Server: withSpan(..., { request }) and trackEvent(..., { request }) join the incoming W3C trace and merge client correlation labels before server frames; ambient via AsyncLocalStorage; detaches when an explicit user differs; surfaces request telemetry failures.
    • Backend: adds customer-request-observability to mint a backend request span that links to the caller’s trace after tenancy is proven; events/batch accepts forwarded refresh_token_id/session_replay_id on server auth (rejects on client), composes [rti-, sri-, srsi-, cs-], and stamps scalar columns; header is versioned, size-limited, and fail-closed.
  • Migration

    • On by default; disable via analytics.spanPropagation.enabled = false.
    • To link server spans/events, pass { request } to withSpan or trackEvent.
    • For split frontend/API domains, add the API origin to analytics.spanPropagation.targets and allow headers in CORS: Access-Control-Allow-Headers: x-hexclave-span-context, traceparent.

Written for commit 96cfe79. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added cross-tier span propagation for analytics, improving end-to-end request tracing.
    • Server-side event tracking can now automatically associate requests with the right session context.
    • Telemetry batches now support additional session identifiers for more accurate attribution.
  • Bug Fixes

    • Tightened validation so client-side requests can’t submit server-derived session fields.
    • Improved session and parent-span linking for forwarded telemetry, reducing mismatched event ancestry.

Update — propagation moved onto the shared wire contract

  • New packages/shared/src/utils/span-context-codec.tsx — the propagation header's encode/decode now lives in shared, so all three readers parse it identically: the client SDK that writes it, server-request-context on the receiving side, and Hexclave's own backend when it resolves the tenancy for a cross-tier request.
  • span-propagation.ts and server-request-context.ts build parent paths from that codec plus the analytics-wire id helpers (uuid ⇄ W3C), instead of hand-rolled parsing.

Slice: 5 files. Part of a stack-wide split of one working tree into the PR each change belongs to.

…erver withSpan({ request }))

Auto-links a customer's BACKEND span to the caller's CLIENT session with zero glue.
The browser attaches one `x-hexclave-span-context` header to same-origin outgoing
fetches; `serverApp.withSpan(type, { request }, fn)` resolves the caller's refresh
token (from the session) plus that header, so the span — and everything created
inside the callback — parents under $refresh-token → $session-replay →
$session-replay-segment, exactly like a browser event.

- span-propagation.ts: versioned base64url header codec, same-origin fetch
  auto-wrapper (idempotent, native header semantics preserved), origin predicate
- server-request-context.ts: AsyncLocalStorage ambient request context
- server-app-impl: withSpan/trackEvent({ request }) resolution; server telemetry
  buffer re-keyed from userId to the full (user, refresh, replay, segment) context;
  propagated custom parents flow as ambient frames
- backend events/batch: accept forwarded refresh_token_id/session_replay_id on
  SERVER auth (rejected on client auth); compose [rti-,sri-,srsi-,cs-] + stamp
  the scalar columns
- client-app-impl: install the fetch wrapper on init behind analytics.spanPropagation
  (same-origin default + exact-origin allowlist); reads the live per-tab id
- tests: 24 propagation unit tests + 2 e2e (server-auth ancestry + client-auth reject)

The framework adapters (tRPC, Convex, oRPC, ElysiaJS) build on this primitive in a
separate stacked branch; with an adapter you never pass { request } yourself.
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Error Error Aug 1, 2026 1:14am
stack-auth-internal-tool Error Error Aug 1, 2026 1:14am
stack-auth-mcp Ready Ready Preview Aug 1, 2026 1:14am
stack-auth-skills Ready Ready Preview Aug 1, 2026 1:14am
stack-backend Error Error Aug 1, 2026 1:14am
stack-dashboard Error Error Aug 1, 2026 1:14am
stack-demo Error Error Aug 1, 2026 1:14am
stack-docs Ready Ready Preview Aug 1, 2026 1:14am
stack-preview-backend Error Error Aug 1, 2026 1:14am
stack-preview-dashboard Error Error Aug 1, 2026 1:14am

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds cross-tier span propagation via a fetch header, extends server-side telemetry (trackEvent/withSpan) to resolve request-scoped ancestry from session tokens and decoded headers, introduces ambient server request context, and updates the analytics batch endpoint and interfaces to accept/validate forwarded refresh_token_id and session_replay_id.

Changes

Cross-tier span propagation and telemetry attribution

Layer / File(s) Summary
Span propagation header codec and fetch installer
packages/template/.../span-propagation.ts, span-propagation.test.ts
New module encodes/decodes a versioned x-hexclave-span-context header, gates propagation by origin policy, and installs an idempotent fetch wrapper; covered by a comprehensive test suite.
Client app wiring
client-app-impl.ts, event-tracker.ts, session-replay.ts
Client app conditionally installs fetch propagation using session replay segment id and ambient spans; EventTracker exposes getSessionReplaySegmentId(); AnalyticsOptions gains spanPropagation config.
Ambient server request context
server-request-context.ts
New AsyncLocalStorage-backed context (with sync fallback) exposes getServerRequestContext/runWithServerRequestContext and the ServerRequestSpanContext type.
Server telemetry request-scoped attribution
server-app-impl.ts
trackEvent/withSpan accept an optional request, resolve ancestry from session tokens and decoded headers, and rebatch/flush telemetry keyed by resolved context.
Server interface signatures
server-app.ts
trackEvent/withSpan interface options add request?: RequestLike with updated documentation.
Analytics batch endpoint attribution
route.tsx, analytics-events-batch.test.ts
Batch schema accepts refresh_token_id/session_replay_id, rejects them for client auth, derives them server-side with fallback logic, and adds e2e coverage for ancestry composition and rejection.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client as Browser (ClientAppImpl)
  participant Fetch as Wrapped fetch
  participant Backend as Analytics Batch Endpoint
  participant ServerApp as StackServerApp (server-app-impl)
  participant Context as ServerRequestContext

  Client->>Fetch: fetch(sameOriginUrl)
  Fetch->>Fetch: encode x-hexclave-span-context header
  Fetch->>Backend: request with span-context header
  Backend->>ServerApp: trackEvent/withSpan({ request })
  ServerApp->>Context: resolve session tokens + decode header
  Context-->>ServerApp: userId, refreshTokenId, sessionReplayId, ancestry
  ServerApp->>Backend: POST /analytics/events/batch (refresh_token_id, session_replay_id)
  Backend->>Backend: validate & derive attribution (server/admin path)
  Backend-->>ServerApp: stored event with composed parent_span_ids
Loading

Possibly related PRs

  • hexclave/hexclave#1520: Both PRs modify the analytics telemetry batch endpoint handler logic for request attribution.

Suggested reviewers: N2D4

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main cross-tier span propagation feature and its client and server API changes.
Description check ✅ Passed The description provides detailed sections for scope, implementation, trust, tests, migration, and follow-up work.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/custom-events-spans-propagation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mantrakp04

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds cross-tier analytics span propagation between the browser SDK, server SDK, and ingestion route. The main changes are:

  • A browser fetch wrapper that attaches a same-origin span context header.
  • Server request context support for withSpan({ request }) and trackEvent({ request }).
  • Backend ingestion support for forwarded refresh and replay identifiers.
  • Tests covering propagation headers, server-auth ingestion, and replay binding.

Confidence Score: 4/5

This is close, but the remaining attribution bug should be fixed before merging.

  • The backend replay binding fixes cover the previously broken ingestion shapes.
  • The server SDK can still combine an explicit user id with an unrelated ambient request session.
  • That can reject valid telemetry batches or write rows with mismatched user and session ancestry.

packages/template/src/lib/hexclave-app/apps/implementations/server-app-impl.ts

Security Review

Server telemetry can still mix an explicit user id with another request's refresh/replay context, causing incorrect analytics ancestry or batch rejection.

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/analytics/events/batch/route.tsx Adds server-auth refresh and replay fields with validation that binds explicit replay ids to the refresh token and user.
apps/backend/src/lib/session-replays.tsx Scopes recent replay lookup by project user when a user is known.
packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts Adds the propagation header codec and a shared global fetch wrapper provider.
packages/template/src/lib/hexclave-app/apps/implementations/client-app-impl.ts Installs fetch propagation from the live client app and builds headers from the current event tracker.
packages/template/src/lib/hexclave-app/apps/implementations/server-app-impl.ts Adds request-scoped server telemetry context and buffer keys, but explicit user attribution can inherit unrelated request session fields.
packages/template/src/lib/hexclave-app/apps/implementations/server-request-context.ts Adds AsyncLocalStorage-backed request context for server spans and events.

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/template/src/lib/hexclave-app/apps/implementations/server-app-impl.ts:1818
**Mixed User Context**

When `trackEvent` or `startSpan` is called inside a request-scoped span with an explicit different `userId`, this branch keeps the ambient request's `refreshTokenId`, replay id, and segment id while only replacing `userId`. That sends a batch where `user_id` belongs to one user but the session ancestry belongs to the caller's request. With a replay id present, the backend rejects the batch because the replay no longer matches the supplied user; with only a refresh token present, the row can be stamped with one user's id and another user's `rti-` parent. Explicit user attribution should not inherit an unrelated ambient session context without validating that the two identities match.

Reviews (3): Last reviewed commit: "feat(analytics): enhance session replay ..." | Re-trigger Greptile

Comment thread apps/backend/src/app/api/latest/analytics/events/batch/route.tsx Outdated
Comment thread apps/backend/src/app/api/latest/analytics/events/batch/route.tsx Outdated
…tier

The header's customParentSpanIds now carry the SAME ambient ancestry a
locally-tracked client event would get — global spans (setGlobalSpan) first,
then enclosing withSpan() frames, each contributing its full frozen chain —
flattened root-first and deduped, instead of only the withSpan frames' leaf
ids. Overflow keeps the NEAREST ancestors (tail), mirroring resolveParentIds.

- event-tracker: expose getAmbientParentRefs() (globals + frames)
- span-propagation: pure flattenParentRefsToIds(refs, extraParents) + nearest-10 cap
- client-app-impl: shared _getSpanPropagationContext + public
  getSpanPropagationHeaders({ parentIds }) escape hatch for non-fetch
  transports (XHR/sendBeacon/WebSocket) and explicit per-request parents
- server-app-impl: client-propagated custom parents now merge BEFORE server
  ambient frames (client ancestry is the outer context), so the root-first
  order reads client chain -> server frames -> explicit parents

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/template/src/lib/hexclave-app/apps/implementations/server-app-impl.ts (1)

1773-1803: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reuse the ambient request context before fetching tokens
fetchNewTokens() does a live token fetch, so each { request } telemetry call pays an avoidable session roundtrip. If getServerRequestContext() is already set, reuse it here and only resolve the session when no ambient context exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/template/src/lib/hexclave-app/apps/implementations/server-app-impl.ts`
around lines 1773 - 1803, The _resolveServerRequestContext method is always
calling _getSession(request) and fetchNewTokens() even when an ambient request
context already exists. Update this flow to first reuse the context from
getServerRequestContext() (or the existing ambient context source used in this
class) and only fall back to resolving the session and fetching tokens when no
ambient context is available. Keep the same return shape for
ServerRequestSpanContext while preserving the existing project-id filtering for
decoded span headers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/template/src/lib/hexclave-app/apps/implementations/server-app-impl.ts`:
- Around line 1773-1803: The _resolveServerRequestContext method is always
calling _getSession(request) and fetchNewTokens() even when an ambient request
context already exists. Update this flow to first reuse the context from
getServerRequestContext() (or the existing ambient context source used in this
class) and only fall back to resolving the session and fetching tokens when no
ambient context is available. Keep the same return shape for
ServerRequestSpanContext while preserving the existing project-id filtering for
decoded span headers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b9a4d4c2-d6e7-44b9-8095-15572ea6bb6b

📥 Commits

Reviewing files that changed from the base of the PR and between d1f93b8 and f4c2ee4.

📒 Files selected for processing (10)
  • apps/backend/src/app/api/latest/analytics/events/batch/route.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-events-batch.test.ts
  • packages/template/src/lib/hexclave-app/apps/implementations/client-app-impl.ts
  • packages/template/src/lib/hexclave-app/apps/implementations/event-tracker.ts
  • packages/template/src/lib/hexclave-app/apps/implementations/server-app-impl.ts
  • packages/template/src/lib/hexclave-app/apps/implementations/server-request-context.ts
  • packages/template/src/lib/hexclave-app/apps/implementations/session-replay.ts
  • packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.test.ts
  • packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts
  • packages/template/src/lib/hexclave-app/apps/interfaces/server-app.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

… header wins + root option)

The browser sync-stack fallback can mix ambient frames from overlapping async
flows into the propagated context (fundamental until TC39 AsyncContext ships —
the span-context loader will pick it up like ALS when it does). Give affected
callers an exact override:

- the auto fetch wrapper never overwrites an explicitly-set
  x-hexclave-span-context header — caller intent beats ambient
- getSpanPropagationHeaders({ parentIds, root }): root drops ambient parents
  (same semantics as TrackOptions.root), so one request can be pinned to
  exactly one span; the segment id (identity, not parenting) always rides
…an handle kit

Browser withSpan frames can mix across concurrently interleaved async flows
(no async-context primitive exists there — the reason for TC39 AsyncContext).
Support BOTH policies via analytics.ambientParenting:

- "exact" (default): a frame is ambient only when provably from the current
  flow — an exact primitive (ALS on servers/edge; AsyncContext in browsers
  once shipped), or the callback's SYNCHRONOUS window on the browser fallback
  (single-threaded sync execution cannot interleave, so prologue-open frames
  are exact by construction). Never wrong; can only under-attach.
- "best-effort": frames also stay ambient across browser awaits (zero-glue),
  accepting the documented cross-flow caveat.

Handle kit on every Span (browser, server, inert) — exact everywhere, both
modes: span.withSpan (nested child), span.run (re-bind for callbacks/timers),
span.getPropagationHeaders (non-fetch transports), span.fetch (pinned
cross-tier header, same origin policy as the auto wrapper, explicit wins).

Internals: sync-stack frames carry a prologueOpen flag (set false the moment
the callback returns its promise); context entry runs synchronously once the
ALS probe settles so sync nesting composes; getAmbientSpanRefs gains an
includeSuspendedSyncFrames read policy; servers fail closed when ALS is
missing. Design validated by Codex (gpt-5.5 xhigh) + a 3-lens design panel.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts">

<violation number="1" location="packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts:168">
P2: Split-port localhost requests do not propagate even when the project’s `allow_localhost` setting is enabled because the client never supplies the callback that enables this branch. Passing the live project flag into the provider would preserve the documented local-development behavior.</violation>

<violation number="2" location="packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts:181">
P2: Configured trusted domains never affect propagation despite this helper documenting them as the zero-config split-frontend/API allowlist, so those API origins remain blocked unless duplicated in `spanPropagation.targets`. Wiring the helper into the client provider or removing the misleading fallback would keep configuration and behavior consistent.</violation>

<violation number="3" location="packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts:363">
P1: Production fetches never create the `$http-client` bridge added here: `beginRequestSpan` is never supplied by the client app, so `httpClientSpanId` and the derived `traceparent` are never emitted. Wiring the event tracker’s request-span opener into the provider would make the advertised bridge work.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

* HEADERS arrive or the request settles with an error (the body stream is
* never touched).
*/
beginRequestSpan?: (info: RequestSpanInfo) => RequestSpanHandle | null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Production fetches never create the $http-client bridge added here: beginRequestSpan is never supplied by the client app, so httpClientSpanId and the derived traceparent are never emitted. Wiring the event tracker’s request-span opener into the provider would make the advertised bridge work.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts, line 363:

<comment>Production fetches never create the `$http-client` bridge added here: `beginRequestSpan` is never supplied by the client app, so `httpClientSpanId` and the derived `traceparent` are never emitted. Wiring the event tracker’s request-span opener into the provider would make the advertised bridge work.</comment>

<file context>
@@ -235,13 +307,60 @@ export function buildFetchInitWithSpanContext(opts: {
+   * HEADERS arrive or the request settles with an error (the body stream is
+   * never touched).
+   */
+  beginRequestSpan?: (info: RequestSpanInfo) => RequestSpanHandle | null,
 };
 
</file context>

* loosen that. Invalid entries are skipped, not fatal — one bad domain in the
* dashboard must not break propagation for the rest.
*/
export function trustedDomainsToPropagationOrigins(trustedDomains: readonly string[]): string[] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Configured trusted domains never affect propagation despite this helper documenting them as the zero-config split-frontend/API allowlist, so those API origins remain blocked unless duplicated in spanPropagation.targets. Wiring the helper into the client provider or removing the misleading fallback would keep configuration and behavior consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts, line 181:

<comment>Configured trusted domains never affect propagation despite this helper documenting them as the zero-config split-frontend/API allowlist, so those API origins remain blocked unless duplicated in `spanPropagation.targets`. Wiring the helper into the client provider or removing the misleading fallback would keep configuration and behavior consistent.</comment>

<file context>
@@ -178,7 +164,34 @@ export function shouldPropagateSpanContext(opts: {
+ * loosen that. Invalid entries are skipped, not fatal — one bad domain in the
+ * dashboard must not break propagation for the rest.
+ */
+export function trustedDomainsToPropagationOrigins(trustedDomains: readonly string[]): string[] {
+  const origins = new Set<string>();
+  for (const domain of trustedDomains) {
</file context>

if (target.protocol !== "http:" && target.protocol !== "https:") return false;
if (opts.selfOrigin !== null && target.origin === opts.selfOrigin) return true;
if (opts.allowedOrigins?.includes(target.origin) === true) return true;
return opts.allowLocalhost === true && isLocalhost(target);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Split-port localhost requests do not propagate even when the project’s allow_localhost setting is enabled because the client never supplies the callback that enables this branch. Passing the live project flag into the provider would preserve the documented local-development behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/template/src/lib/hexclave-app/apps/implementations/span-propagation.ts, line 168:

<comment>Split-port localhost requests do not propagate even when the project’s `allow_localhost` setting is enabled because the client never supplies the callback that enables this branch. Passing the live project flag into the provider would preserve the documented local-development behavior.</comment>

<file context>
@@ -178,7 +164,34 @@ export function shouldPropagateSpanContext(opts: {
   if (opts.selfOrigin !== null && target.origin === opts.selfOrigin) return true;
-  return opts.allowedOrigins?.includes(target.origin) ?? false;
+  if (opts.allowedOrigins?.includes(target.origin) === true) return true;
+  return opts.allowLocalhost === true && isLocalhost(target);
+}
+
</file context>

The span-context header codec carries W3C context, and
customer-request-observability.ts opens a span per customer-facing backend
request that links to the caller's trace once the route proves the project
may see it.
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