Skip to content

fix: harden RSC initialization and remote module fetches - #3070

Merged
kojiwakayama merged 8 commits into
mainfrom
fix/lazy-rsc-react-version
Jul 24, 2026
Merged

fix: harden RSC initialization and remote module fetches#3070
kojiwakayama merged 8 commits into
mainfrom
fix/lazy-rsc-react-version

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • defer project React version discovery until an RSC render actually needs it
  • retry transient remote module fetch failures with bounded backoff while preserving permanent HTTP failures
  • keep concurrent waiters aligned with the complete retry budget and cancel failed response bodies
  • remove raw cache identities from timeout logs and redact credential-bearing module URLs
  • remove load-sensitive timing assumptions from Redis, citation, router, and filesystem tests
  • patch vulnerable PostCSS and protobufjs transitive resolutions
  • restore the documented Storybook production build by resolving the Deno config explicitly
  • clarify the public client entrypoint documentation
  • sync current main

Release target

This PR releases v0.1.1127. Version v0.1.1126 already exists from the preceding main commit, so both deno.json and src/utils/version-constant.ts intentionally advance to 0.1.1127.

Why

The 0.1.1125 release exposed two production reliability issues: constructing an RSC handler could leave a pending Deno.stat operation, and a transient esm.sh failure could abort a render immediately. Review and full-suite verification then exposed adjacent security, determinism, and developer-workflow defects. This PR fixes each at its existing boundary rather than leaving known regressions for a later release.

Verification

  • full pre-push gate: 2,644 suites, 21,458 steps passed
  • verify:quick passed, including format, lint, public docs, and typecheck
  • focused timeout and HTTP cache tests: 60 steps passed
  • Storybook boundary regression and production build passed
  • OpenTelemetry extension frozen-lock tests: 17 steps passed
  • npm audit: 0 vulnerabilities
  • permanent 404 responses remain single-attempt failures
  • all review threads addressed and resolved

Registry and cache-only consumers should not start project package detection. Resolve the React version on first render or page use and memoize that promise.

Constraint: Constructor-only registry operations must not start filesystem work

Rejected: Configure React only in tests | hides the production side effect

Rejected: Add asynchronous cache teardown | spreads lifecycle complexity without owning the work

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep RSC handler construction side-effect free

Tested: focused RSC suites with --trace-leaks; coverage shard 2

Not-tested: staging runtime before release
SSR HTTP module loading failed on the first temporary upstream response. Retry retryable statuses and network failures with bounded backoff while preserving immediate failure for permanent responses, and publish the fixes as 0.1.1126.

Constraint: Remote module loading depends on third-party CDN availability

Rejected: Retry the complete binary test only | leaves production renders exposed to the same outage

Rejected: Retry permanent 4xx responses | adds latency without a recovery path

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep retries bounded and preserve immediate permanent-response failures

Tested: HTTP bundle cache suite; focused RSC suites; coverage shard 2; deno task verify:quick

Not-tested: live CDN outage longer than the bounded retry window
@kojiwakayama
kojiwakayama requested a review from kwakayama as a code owner July 24, 2026 14:25
Copilot AI review requested due to automatic review settings July 24, 2026 14:25

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.

Pull request overview

This PR improves runtime reliability around RSC initialization and remote ESM module fetching, addressing leaked/pending filesystem ops during handler construction and transient upstream fetch failures during render.

Changes:

  • Defer project React version discovery in RSC until a render path actually needs it (via a lazy callback).
  • Add bounded retry with backoff for transient HTTP module fetch failures, with new tests covering retry vs permanent failure behavior.
  • Bump package/version constants to 0.1.1126.

Verification status (from PR description):

  • Focused RSC and HTTP cache tests with --trace-leaks: passed
  • Coverage shard 2/8: passed
  • verify:quick: passed
  • Full unit suite: passed

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/utils/version-constant.ts Bumps shared version constant to 0.1.1126.
src/transforms/esm/http-cache.ts Adds retrying fetchHttpModule() wrapper for transient HTTP failures and uses it in the cache path.
src/transforms/esm/http-cache.test.ts Adds tests for transient retry behavior (502) and non-retry for permanent failure (404).
src/server/services/rsc/orchestrators/render-handler.ts Switches reactVersion option from a promise to a lazy callback and updates call sites.
src/server/services/rsc/orchestrators/handler.ts Lazily resolves and memoizes React version only when needed by renderer/page handling.
src/server/services/rsc/endpoints/handler-registry.test.ts Adds regression test ensuring handler construction does not trigger dependency inspection (no Deno.stat).
deno.json Bumps package version to 0.1.1126.
Comments suppressed due to low confidence (1)

src/transforms/esm/http-cache.ts:331

  • When the fetch returns a non-OK response, the code throws immediately without consuming or canceling the response body. In Deno, leaving the body unread can keep the underlying connection/resource open longer than necessary and may show up as a leak under --trace-leaks (especially on repeated failures). Cancel the body before throwing to release resources promptly.
    const response = await fetchHttpModule(normalizedUrl);

    if (!response.ok) {
      throw BUILD_FAILED.create({ detail: `Failed to fetch ${normalizedUrl}: ${response.status}` });
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0d51ff3e6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/transforms/esm/http-cache.ts Outdated
The new bounded HTTP retry loop can outlive the previous in-flight waiter deadline. Give callers a complete wait window, align the HTTP cache waiter with the retry budget, and discard final error response bodies before surfacing BUILD_FAILED.

Constraint: A three-attempt fetch may consume roughly 90 seconds before backoff and scheduling overhead.

Rejected: Remove the in-flight deadline | a fetch implementation that ignores abort could block every waiter indefinitely.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep the in-flight wait budget at least as large as the owning HTTP retry budget.

Tested: focused in-flight and HTTP cache suites with trace-leaks (54 steps), verify:quick

Not-tested: wall-clock 90-second upstream timeout path; policy is covered with an injected short wait window.
Copilot AI review requested due to automatic review settings July 24, 2026 14:47

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.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Remote module failures can occur after response headers, so the shared retry boundary now covers fetch and body consumption together. Each failed response is discarded, transient failures retain one bounded owner, and observability only receives credential-safe URL details.

Constraint: HTTP response bodies can fail or stall after headers have already succeeded.

Rejected: Keep the manual retry loop | it duplicated shared backoff behavior and left body reads outside the attempt timeout.

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep fetch, body consumption, and failed-body cancellation inside the same HTTP module retry attempt.

Tested: deno task verify:quick; 58 trace-leak-checked HTTP cache and in-flight steps; red-green coverage for network, body-read, status exhaustion, cancellation, and credential safety.

Not-tested: Live upstream CDN fault injection.
Copilot AI review requested due to automatic review settings July 24, 2026 14:57

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.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/transforms/esm/http-cache.ts:408

  • normalizedUrl may include credential-bearing query params (for example ?access_token=...). This block logs and surfaces normalizedUrl directly in log context and the BUNDLE_ERROR detail, which can leak secrets. Use the already-imported sanitizeUrlForSpan() (or another URL-sanitizer) when emitting the URL in logs/errors, while still fetching with the full normalized URL.
    httpCacheLog.debug("Fetching from network", { url: normalizedUrl });
    const fetchedModule = await fetchHttpModule(normalizedUrl);
    let code = fetchedModule.code;

    const contentType = fetchedModule.contentType;
    const isHtmlContent = contentType.includes("text/html") || looksLikeHtmlNotJs(code);

    if (isHtmlContent) {
      logger.error(
        "[HTTP-CACHE] Received HTML instead of JavaScript, likely an esm.sh error page",
        {
          url: normalizedUrl,
          contentType,
          preview: code.slice(0, 200),
        },
      );
      throw BUNDLE_ERROR.create({
        detail:
          `Received HTML instead of JavaScript from ${normalizedUrl}. The package may not exist or failed to build on esm.sh.`,
      });

The lint gate checks new test files more strictly than the no-check unit runner, so rejected values must be narrowed before their messages are inspected.

Constraint: Preserve the existing runtime assertions and retry coverage.

Confidence: high

Scope-risk: narrow

Tested: deno check --no-lock src/transforms/esm/http-cache.test.ts; deno fmt --check; focused test with --trace-leaks

Not-tested: Full repository suite for this assertion-only follow-up
Copilot AI review requested due to automatic review settings July 24, 2026 15:04

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.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment thread src/transforms/esm/http-cache.ts Outdated
Comment thread src/server/services/rsc/endpoints/handler-registry.test.ts
Sanitize credential-bearing module URLs before logs and user-visible bundle errors, and scope the lazy React version assertion to its project path so unrelated Deno.stat calls cannot make the regression test flaky.

Constraint: HTTP cache URLs may contain provider credentials and test processes may perform unrelated filesystem probes.

Rejected: Keep the HTML response preview in error logs | upstream bodies can contain secrets and do not add actionable cache diagnostics.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep externally supplied URLs sanitized in every diagnostic and count only project-owned probes in global API stubs.

Tested: deno test -A --no-check focused cache and handler suites; deno fmt --check; deno task verify:quick

Not-tested: live upstream HTML failure carrying real provider credentials
Copilot AI review requested due to automatic review settings July 24, 2026 16:47

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.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread src/transforms/esm/in-flight-manager.ts Outdated
Comment thread src/transforms/esm/http-cache.test.ts Fixed
The release candidate was functionally correct, but review and complete validation uncovered an unnecessary jitter call, an imprecise security assertion, three load-sensitive tests, vulnerable transitive packages, internal public-doc wording, and a broken documented Storybook build. Keep each correction at its existing boundary and add regression evidence where behavior changed.

Constraint: Preserve the v0.1.1126 release scope and introduce no new dependency.

Rejected: Ignore failures outside the original RSC regression | would ship known security and developer-workflow defects.

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep caller timeout overrides deterministic and keep the Storybook Deno config alias explicit.

Tested: verify:quick; 82 focused test steps; Storybook boundary test and production build; frozen OpenTelemetry tests; npm audit.

Not-tested: Full pre-push suite after this final commit; GitHub CI is the authoritative full-suite rerun.
Copilot AI review requested due to automatic review settings July 24, 2026 17:17
@kojiwakayama
kojiwakayama enabled auto-merge July 24, 2026 17:19

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.

Pull request overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • storybook/package-lock.json: Generated file

Comment thread src/transforms/esm/in-flight-manager.ts
Sync the release branch with current main, advance its patch version beyond the already-published 0.1.1126, and remove cache identities from in-flight timeout logging so credential-bearing URLs cannot reach logs.

Constraint: v0.1.1126 was published from main while this PR was still pending.

Rejected: Redact selected query parameters | omitting the unused cache identity closes the entire logging channel.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Do not add raw HTTP cache identities back to logs.

Tested: focused in-flight and HTTP cache tests (60 steps); deno task verify:quick

Not-tested: staging release deployment pending merge
Copilot AI review requested due to automatic review settings July 24, 2026 17:46
@kojiwakayama
kojiwakayama disabled auto-merge July 24, 2026 17:48
@kojiwakayama
kojiwakayama enabled auto-merge July 24, 2026 17:48

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.

Pull request overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • storybook/package-lock.json: Generated file

Comment thread deno.json
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

@kwakayama All CI checks are green and every review thread is addressed/resolved. This v0.1.1127 gate now only needs the required code-owner/last-push approval; #3077 is stacked behind it for v0.1.1128.

@kojiwakayama
kojiwakayama added this pull request to the merge queue Jul 24, 2026
Merged via the queue into main with commit 160c0d8 Jul 24, 2026
31 checks passed
@kojiwakayama
kojiwakayama deleted the fix/lazy-rsc-react-version branch July 24, 2026 18:34
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.

4 participants