Skip to content

fix(analog): link server calls only to the request they ran under - #405

Merged
erkamyaman merged 1 commit into
mainfrom
fix/analog-ssr-request-linking
Oct 13, 2026
Merged

erkamyaman merged 1 commit into
mainfrom
fix/analog-ssr-request-linking

Conversation

@erkamyaman

@erkamyaman erkamyaman commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

What and why

On an Analog app, a server render has no Angular REQUEST token, so the HTTP interceptor linked its calls to whichever traced request was the only one in flight. An untraced render running alongside a traced one had its server calls shown under the other page's request, and two overlapping traced renders lost their links altogether.

The tracers now keep the request id (or an "untraced" marker) in an AsyncLocalStorage shared through the SSR registry:

  • withPangularSsr and the Node middleware run the handler inside it.
  • pangularSsrEventHandler (h3/Nitro) enters it for the rest of the request, since the render runs after the handler returns.
  • The interceptor reads it, keeps the first value per app so a call that loses the async context still finds its own request, and links nothing when no tracer saw the render.

The storage is created with process.getBuiltinModule('node:async_hooks'), so the app bundle gets no static Node import. Renders with Angular's REQUEST (Express) still use the header, and nothing changes while the inspector is off.

The docs limitation in the Analog guide and the SSR HTTP page is replaced with the new behaviour.

How it was verified

  • pnpm commit:check
  • pnpm format:check
  • pnpm typecheck
  • pnpm test:devtools: 3053 tests pass. The 5 new tests in ssr-request-context.test.ts (real h3 and Node servers with overlapping traced and untraced renders) fail before the fix.
  • pnpm skills:check
  • Docs in apps/docs updated and pnpm docs:build passes
  • pnpm extension:build (no app/ change)
  • Checked in the browser with axe (no UI change)

Manual check in examples/analog dev: four traced and two untraced requests overlapping. Each traced render's call linked to its own Server-Timing id, and the untraced renders' calls stayed unlinked.

Notes for reviewers

  • The production Nitro build was not run by hand. nitropack and h3 call middleware directly, so the enterWith context carries into the render.
  • Pre-existing, not changed here: in Analog dev every traced request is recorded with URL /index.html, because Vite rewrites req.url before the tracer reads it.

Summary by CodeRabbit

  • Bug Fixes

    • Server-side calls are now associated with the correct request when renders overlap, reducing the chance that activity appears under the wrong trace.
    • Calls from renders without a traced request remain unlinked instead of inheriting another render’s trace.
    • Request association still expires after five minutes without activity.
  • Documentation

    • Updated the Analog guide and SSR HTTP inspector documentation to describe request association and overlapping renders.

A render without Angular's REQUEST token, as in Analog, was linked to
whichever traced request was the only one in flight. An untraced render
running alongside a traced one had its server calls shown under the
other page's request, and two overlapping traced renders lost their
links altogether.

The tracers now set the request id, or an untraced marker, in an
AsyncLocalStorage shared through the SSR registry: withPangularSsr and
the Node middleware run the handler inside it, and the h3 event handler
enters it for the rest of the request. The interceptor reads it, keeps
the first value per app so a call that loses the async context still
finds its own request, and links nothing when no tracer saw the render.
@github-actions github-actions Bot added area: package The ng-devtools package (packages/ng-devtools) area: docs The documentation site labels Oct 13, 2026
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5a555791-05e0-40bf-afae-0379b406c257
📥 Commits

Reviewing files that changed from the base of the PR and between bed696a and 360fab4.

📒 Files selected for processing (10)
  • apps/docs/src/content/guides/analog.md
  • apps/docs/src/content/inspectors/ssr-http.md
  • packages/devtools/src/__tests__/ssr-fetch.test.ts
  • packages/devtools/src/__tests__/ssr-request-context.test.ts
  • packages/devtools/src/http.ts
  • packages/devtools/src/ssr-context.ts
  • packages/devtools/src/ssr-fetch.ts
  • packages/devtools/src/ssr-middleware.ts
  • packages/devtools/src/ssr-registry.ts
  • packages/devtools/src/ssr.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

SSR tracing now propagates request IDs through async context. Server calls use the current context to find their request instead of inferring a match from the only active traced request. Tests cover overlapping traced and untraced renders, and the Analog guides describe the updated association behavior.

Changes: SSR request context

Layer / File(s) Summary
Request context storage and registry
packages/devtools/src/ssr-context.ts, packages/devtools/src/ssr-registry.ts
The registry now optionally caches AsyncLocalStorage. New helpers run or enter traced and untraced contexts, and resolve the current request ID for an app. The sole-active-request helper was removed.
Scope SSR request execution
packages/devtools/src/ssr-fetch.ts, packages/devtools/src/ssr-middleware.ts, packages/devtools/src/ssr.ts, packages/devtools/src/__tests__/ssr-request-context.test.ts
SSR handlers and middleware now run traced HTML requests in a context with the trace ID. Other requests run in an untraced context. Tests cover overlapping traced and untraced renders across middleware and render paths.
Associate server calls and validate lookups
packages/devtools/src/http.ts, packages/devtools/src/__tests__/ssr-fetch.test.ts, apps/docs/src/content/guides/analog.md, apps/docs/src/content/inspectors/ssr-http.md
The HTTP interceptor now reads the request ID from the current async context and gets the page URL from that request’s registry entry. Tests cover explicit context lookup and inactivity expiry. The Analog documentation describes the updated association behavior.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant createSsrMiddleware
  participant runInSsrRequest
  participant next
  participant HTTPInterceptor
  participant currentSsrRequest
  createSsrMiddleware->>runInSsrRequest: Run downstream with trace ID or undefined
  runInSsrRequest->>next: Execute middleware in request context
  next->>HTTPInterceptor: Make server-side HTTP call
  HTTPInterceptor->>currentSsrRequest: Resolve the current request ID
Loading
Merge Risk: ⚪ Minimal · up to 360fa

Server calls in Analog SSR are now linked to the request they ran under, and calls from untraced renders stay unlinked. No concrete merge-blocking risk was found in the supplied changes. The production Nitro build was not checked by hand, which is normal pre-merge validation uncertainty.

Security architecture risk: 🔵 Low · up to 360fa

The change replaces request guessing with explicit per-request identity and improves isolation between overlapping renders. No introduced security issue was established. Remaining uncertainty concerns request-context ownership and cleanup in execution paths beyond the covered server integrations.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed identity mechanism spans SSR work sharing one JavaScript process, including multiple package copies. Incorrect association could affect inspector records and existing page-scoped fault behavior, making request ownership relevant beyond display accuracy.

Trust Boundaries and Controls

  • observed — Active-map membership is a correlation check, not independent transport ownership proof. Recording-disabled producers already bypass header normalization in the base, and the Angular REQUEST consumer is unchanged. The conditional replay exposure identified in that path therefore predates this PR.

Resilience and Maintainability Implications

  • inferred — Per-injector pinning depends on a render-scoped injector. The inspected Analog integration invokes bootstrapApplication through renderApplication for each render, and Angular destroys the created platform in a finally block. This counters ordinary cross-render injector reuse there, but does not establish ownership for every custom integration.
Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 8 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: linking Analog server calls only to their originating SSR request.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 8 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@nx-cloud

nx-cloud Bot commented Oct 13, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 360fab4

Command Status Duration Result
nx affected -t test build ✅ Succeeded 2m 32s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-13 18:12:40 UTC

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
360fab4 2026-10-13T18:11:08.178Z View logs ↗

@erkamyaman
erkamyaman merged commit e91767b into main Oct 13, 2026
9 of 10 checks passed
@erkamyaman
erkamyaman deleted the fix/analog-ssr-request-linking branch October 13, 2026 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: docs The documentation site area: package The ng-devtools package (packages/ng-devtools)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant