Skip to content

fix(core): repair default client, packaging, and gateway ranking (2.0.2) - #257

Merged
vilenarios merged 8 commits into
mainfrom
fix/core-reliability-2.0.2
Aug 13, 2026
Merged

vilenarios merged 8 commits into
mainfrom
fix/core-reliability-2.0.2

Conversation

@vilenarios

@vilenarios vilenarios commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Evaluation of wayfinder against the live AR.IO Solana network, and fixes for what it turned up. Ships as core/react 2.0.2, extension 2.0.1.

Why these were invisible

Each of the three main defects is something the existing suite structurally could not see:

Defect Why it hid
new Wayfinder() served 0/20 requests The default-config test inspects the object graph and never issues a request
Published package unimportable in Node axios is hoisted into the root node_modules by another workspace, so it resolves in-repo and nowhere else
sortBy/sortOrder silently ignored Nothing exercised the on-chain registry — every test stubs the gateways provider

Fixes

  • new Wayfinder() could not serve a single request. The gateways provider is injected only if (routingSettings?.strategy), so the default PingRoutingStrategy never got one and threw No gateways available unconditionally. createWayfinderClient() was unaffected. 0/20 → 20/20.
  • npm i @ar.io/wayfinder-core + import failed outright with ERR_MODULE_NOT_FOUND: axios. @dha-team/arbundles imports it without declaring it (still true on 1.0.4, the latest). Declared explicitly — the /web subpath looks like a fix but breaks signature verification with a TypeError.
  • NetworkGatewaysProvider ignored sortBy/sortOrder. The SDK's Solana paginate() slices without sorting, and the provider also exited pagination early — so limit: 20 returned 20 arbitrary gateways, not the top 20 staked. Now sorts client-side over the full registry, verified against ground truth for asc, desc, and dotted paths.
  • Ping strategies probed the sandboxed content URL, which forces the gateway to resolve data and needs wildcard DNS/TLS. At the default 1s budget: ~23% success vs ~85% for the gateway root. The gap is latency, not breakage — sandboxed URLs reach ~78% given 10s. Now probes the root, configurable via a new probePath.
  • TrustedPeersGatewaysProvider returned [] silently when a gateway served an empty gateways map, surfacing later as an opaque No gateways available. Now retries, then throws naming the endpoint. Both the composite and cache providers treat that as a fall-back signal.
  • Strict-mode verification leaked an unhandled rejection — the verification promise is created eagerly but awaited only after the stream drains, so a header-based strategy rejecting immediately would kill a Node process under the default --unhandled-rejections=throw.
  • Chunk retrieval now reports an unsupported endpoint instead of Chunk transaction ID mismatch … Got: null.

Extension

The mainnet Solana RPC was a provider URL with an embedded token that now returns UNAUTHORIZED (verified with and without extension origin/referer headers). Registry sync was failing silently and routing was collapsing onto the single hardcoded fallback gateway — decentralized routing gone, with no user-visible signal.

Now uses the SDK's MAINNET_RPC_URL. It's the only keyless endpoint that serves getProgramAccounts, which the registry read requires — I tested 15 alternatives; the rest 403/402/429 or need a key. At one sync per 24h from each user's own IP, it sits well inside the per-IP limits. No credential ships in the bundle any more; an extension bundle is public, so a token there was never a secret.

rpcUrl is only written when absent, so existing installs would have kept the dead endpoint forever — migrateRetiredMainnetRpcUrls() rewrites it and forces a resync.

New coverage

  • src/e2e/ — 38 tests across all four extension points against the live network, including the Solana registry path that nothing previously touched, plus regression guards for the zero-config constructor.
  • test:package — packs, installs into a clean ESM project, imports. Verified it catches the real bug: removing axios reproduces the exact ERR_MODULE_NOT_FOUND. Worth wiring into the release workflow.

Verification

unit 132/132 · integration 60/60 · e2e 36/38 · packaging check passes · biome clean (86 files)

The 2 e2e failures are gateway-side and expected: /chunk/<offset>/data is unusable network-wide (0/25 top-staked gateways). turbo-gateway returns correct bytes without the X-Arweave-Chunk-* headers; the rest 404. Documented in src/e2e/README.md as expected rather than silenced.

Notes for review

  • Base is main: origin/develop is 870 commits behind, and stable releases publish from main.
  • Two test expectations changed because they were passing for accidental reasons — one relied on the ping bug short-circuiting before the HEAD check (and preferred.com is a real site that answers 200), the other asserted the content-URL probing being replaced here.
  • Docs updated: the core README advertised the early-pagination exit as a resiliency feature when it was the sorting bug; the root README promised a yarn test that fails on the CLI package; the extension CLAUDE.md had no Solana content at all.
  • @ar.io/wayfinder-core@2.0.1 is on the alpha dist-tag — latest is still 2.0.0. Verify npm dist-tag ls after release. wayfinder-react tagged correctly from the same run.

Not addressed

Chunk retrieval left as-is pending gateway support. Two upstream reports worth filing: @dha-team/arbundles should declare axios, and the SDK's Solana paginate() should apply sortBy/sortOrder — it affects ~20 paginated reads for every SDK consumer, though wayfinder no longer depends on it.

🤖 Generated with Claude Code

https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y

Summary by CodeRabbit

  • New Features

    • Added resilient gateway discovery with fallback providers, caching, retries, sorting, and dynamic round-robin selection.
    • Added configurable gateway health probing and improved chunk retrieval error messages.
    • Added package verification and live end-to-end coverage for routing, retrieval, gateways, and verification.
    • Added startup migration from retired Solana RPC endpoints while preserving custom configurations.
  • Bug Fixes

    • Improved strict verification failure handling and gateway pagination reliability.
    • Fixed React provider memoization and server-side compatibility.
  • Documentation

    • Expanded setup, testing, architecture, configuration, routing, and release documentation.
  • Chores

    • Released core and React updates, plus extension version updates.

Three defects that the monorepo's own test suite could not observe, plus the
e2e coverage that would have caught them.

- new Wayfinder() could not serve a single request. The gateways provider was
  injected only when the caller supplied their own strategy, so the default
  PingRoutingStrategy resolved an empty candidate list and threw
  "No gateways available" on every call. Measured 0/20 before, 20/20 after.
- The published package could not be imported in Node at all.
  @dha-team/arbundles imports axios without declaring it, and src/index.ts
  reaches it via the signature strategy. This was masked in-repo because
  another dependency hoists axios into the root node_modules.
- NetworkGatewaysProvider ignored sortBy/sortOrder. The Solana backend of
  @ar.io/sdk accepts both and applies neither, so limit selected an arbitrary
  subset rather than the top-ranked gateways. Sorting now happens client-side
  over the full registry.

Also: ping strategies probe the gateway root instead of the sandboxed content
URL (~85% vs ~23% success at the default 1s budget), TrustedPeersGatewaysProvider
retries and then fails loudly instead of silently returning an empty list,
strict-mode verification failures no longer leak an unhandled rejection, and
chunk retrieval reports an unsupported endpoint instead of blaming the data.

The extension's mainnet Solana RPC was a provider URL with an embedded token
that now returns UNAUTHORIZED, silently breaking registry sync and collapsing
routing onto a single hardcoded gateway. It now uses the SDK's MAINNET_RPC_URL,
with a migration for installs still holding the dead endpoint.

Adds src/e2e/ (38 tests over all four extension points against the live AR.IO
Solana network) and test:package, which packs and installs the tarball into a
clean project — the only check that can catch an undeclared dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y
@vilenarios
vilenarios requested a review from a team as a code owner August 12, 2026 19:07
@changeset-bot

changeset-bot Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9cfa6d8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@vilenarios, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0892cefd-8567-4d0d-928b-6c637a9c8298

📥 Commits

Reviewing files that changed from the base of the PR and between 04cf88d and 9cfa6d8.

📒 Files selected for processing (1)
  • packages/wayfinder-core/src/e2e/README.md

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7979c951-6dd9-461f-9e4f-b99cc422f676

📥 Commits

Reviewing files that changed from the base of the PR and between 899a443 and 04cf88d.

📒 Files selected for processing (1)
  • packages/wayfinder-react/src/components/wayfinder-provider.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/wayfinder-react/src/components/wayfinder-provider.test.tsx

📝 Walkthrough

Walkthrough

The pull request updates Wayfinder core gateway and routing behavior, adds live-network and package validation, migrates extension RPC settings, improves React provider memoization, and refreshes repository and package documentation for the 2.0.2 release.

Changes

Wayfinder reliability and release

Layer / File(s) Summary
Gateway providers and routing behavior
packages/wayfinder-core/src/gateways/*, packages/wayfinder-core/src/routing/*, packages/wayfinder-core/src/wayfinder.*, packages/wayfinder-core/src/client.ts
Adds composite and cached gateway providers, full-registry pagination with retries, dynamic round-robin pools, gateway-root probing, and corrected provider injection.
Retrieval and verification failures
packages/wayfinder-core/src/retrieval/chunk.ts, packages/wayfinder-core/src/utils/verify-stream.ts
Reports unsupported chunk endpoints when metadata headers are absent and handles early verification rejection safely.
Live-network and package validation
packages/wayfinder-core/src/e2e/*, packages/wayfinder-core/scripts/verify-package.mjs, packages/wayfinder-core/package.json, README.md
Adds live gateway, routing, retrieval, and verification coverage, plus tarball installation and export validation.
Extension migration and React provider integration
packages/wayfinder-extension/src/*, packages/wayfinder-extension/package.json, packages/wayfinder-react/src/*, packages/wayfinder-react/package.json
Uses SDK RPC presets, migrates retired stored endpoints, invalidates registry metadata, and stabilizes cached React provider state.
Repository and package guidance
CLAUDE.md, packages/wayfinder-core/README.md, packages/wayfinder-core/CHANGELOG.md, packages/wayfinder-extension/README.md, packages/wayfinder-extension/CLAUDE.md, packages/wayfinder-react/CHANGELOG.md
Documents the Solana architecture, commands, routing and provider behavior, test limits, release workflow, and package changes.

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

Mergeability Score: 🔵 Low · up to 04cf8

The PR improves chunk-retrieval error reporting but still leaves the coverage matrix describing chunk retrieval as working despite documented network-wide failures. This bounded documentation risk should be corrected or explicitly accepted before release; the code changes otherwise appear mergeable.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Wayfinder
  participant GatewayProvider
  participant RoutingStrategy
  participant Gateway
  Client->>Wayfinder: request data
  Wayfinder->>GatewayProvider: resolve gateways
  GatewayProvider-->>Wayfinder: return gateway pool
  Wayfinder->>RoutingStrategy: select gateway
  RoutingStrategy->>Gateway: probe configured path
  Gateway-->>RoutingStrategy: return health response
  RoutingStrategy-->>Wayfinder: return selected gateway
  Wayfinder->>Gateway: retrieve and verify data
  Gateway-->>Client: return response
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main core fixes for the default client, package importability, and gateway ranking.
Description check ✅ Passed The description clearly explains the problems, fixes, testing results, extension changes, known failures, and follow-up work.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.
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.
✨ 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 fix/core-reliability-2.0.2

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.

@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.

Actionable comments posted: 9

🧹 Nitpick comments (11)
packages/wayfinder-core/src/gateways/network.ts (1)

59-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the new any values with a gateway interface.

sortGateways introduces any[] and any values. Use an SDK gateway type or a local structural interface for sortable gateway records. This keeps invalid gateway shapes and sort access errors detectable before runtime.

As per coding guidelines, packages/*/src/**/*.{ts,tsx} requires: “Code to interfaces, prefer composition over inheritance, prefer type safety.”

🤖 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/wayfinder-core/src/gateways/network.ts` around lines 59 - 72, Update
sortGateways and its sortValue helper to use the SDK gateway type or a local
structural interface instead of any and any[]. Type the nested sort-property
access safely while preserving the existing numeric conversion and
ascending/descending ordering behavior.

Source: Coding guidelines

packages/wayfinder-core/src/e2e/verification.e2e.test.ts (2)

96-107: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Drain or cancel the probe response bodies.

gatewayServingTx reads only res.ok. It leaves each response body unconsumed. firstGatewayWhere can probe several gateways, and each undrained body holds its socket until garbage collection. Call res.body?.cancel() after the status check.

♻️ Proposed change to release each probe socket
     async (gateway) => {
       const res = await fetch(`${gateway}/tx/${txId}`, {
         signal: AbortSignal.timeout(20_000),
       });
+      await res.body?.cancel();
       return res.ok;
     },
🤖 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/wayfinder-core/src/e2e/verification.e2e.test.ts` around lines 96 -
107, Update gatewayServingTx so every fetch probe cancels or drains res.body
immediately after evaluating res.ok, including unsuccessful responses, before
returning the status result. Preserve the existing gateway selection behavior
and timeout handling.

184-209: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the obsolete unhandled-rejection workaround.

tapAndVerifyReadableStream now handles the verification rejection, and verify catches the strict body-read failure. Remove the listener, leaked, and related comments.

🤖 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/wayfinder-core/src/e2e/verification.e2e.test.ts` around lines 184 -
209, Remove the unhandled-rejection workaround surrounding the verify call:
delete the leaked collection, captureLeak handler, process listener
registration/removal, and related comments. Keep the existing verify invocation
and result assertion in the test unchanged.
packages/wayfinder-core/src/e2e/gateways.e2e.test.ts (2)

50-58: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

A rejected snapshot promise stays cached and fails every later test.

registrySnapshot ??= stores the promise, not the result. If the registry read rejects after withRpcRetry exhausts its attempts, the rejected promise remains assigned. Every later call to joinedGateways() then rejects with the same stale error, and no retry can recover. Clear the cache on rejection.

♻️ Proposed change to avoid caching a rejection
 let registrySnapshot: Promise<any[]> | undefined;
 const joinedGateways = () => {
   registrySnapshot ??= withRpcRetry(() =>
     ario.getGateways({ limit: 1000 }),
-  ).then(({ items }) =>
-    items.filter((gateway: any) => gateway.status === 'joined'),
-  );
+  )
+    .then(({ items }) =>
+      items.filter((gateway: any) => gateway.status === 'joined'),
+    )
+    .catch((error) => {
+      registrySnapshot = undefined;
+      throw error;
+    });
   return registrySnapshot;
 };
🤖 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/wayfinder-core/src/e2e/gateways.e2e.test.ts` around lines 50 - 58,
Update joinedGateways so a rejected registrySnapshot promise clears
registrySnapshot before propagating the error, allowing subsequent calls to
perform a fresh withRpcRetry request while preserving successful promise
caching.

50-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace the any annotations in the end-to-end helpers. The registry record shape and the caught error are both left untyped, which drops the type safety the guidelines ask for. The coding guidelines state: "Code to interfaces, prefer composition over inheritance, prefer type safety."

  • packages/wayfinder-core/src/e2e/gateways.e2e.test.ts#L50-L58: type registrySnapshot with the SDK gateway type exported by @ar.io/sdk instead of Promise<any[]>, and drop the (gateway: any) annotation on the filter callback. The same applies to the (gateway: any) callbacks at Lines 118 and 124.
  • packages/wayfinder-core/src/e2e/fixtures.ts#L133-L135: catch as unknown and narrow before reading context.statusCode and message.

As per coding guidelines: "Code to interfaces, prefer composition over inheritance, prefer type safety."

🤖 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/wayfinder-core/src/e2e/gateways.e2e.test.ts` around lines 50 - 58,
Replace the untyped gateway records in joinedGateways and the gateway filter
callbacks at lines 118 and 124 of
packages/wayfinder-core/src/e2e/gateways.e2e.test.ts with the SDK gateway type
exported by `@ar.io/sdk`, removing the any annotations. In
packages/wayfinder-core/src/e2e/fixtures.ts lines 133-135, catch errors as
unknown and narrow them before accessing context.statusCode or message.

Source: Coding guidelines

packages/wayfinder-core/src/e2e/fixtures.ts (1)

93-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Guard against a non-numeric E2E_TIMEOUT_MS.

Number('fast') returns NaN. The suite then passes NaN as the timeout option to describe. Fall back to the default when the parsed value is not a finite number.

♻️ Proposed change to validate the parsed timeout
-export const E2E_TIMEOUT_MS = Number(process.env.E2E_TIMEOUT_MS ?? 90_000);
+const parsedTimeoutMs = Number(process.env.E2E_TIMEOUT_MS);
+export const E2E_TIMEOUT_MS = Number.isFinite(parsedTimeoutMs)
+  ? parsedTimeoutMs
+  : 90_000;
🤖 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/wayfinder-core/src/e2e/fixtures.ts` at line 93, Update the
E2E_TIMEOUT_MS initialization in fixtures.ts to validate the parsed environment
value with a finite-number check, falling back to 90_000 whenever Number(...)
produces NaN or another non-finite value; retain valid numeric timeout values.
packages/wayfinder-core/src/e2e/routing.e2e.test.ts (1)

209-225: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

A duplicate host in POOL makes this assertion impossible to satisfy.

POOL at Line 51 concatenates TRUSTED_GATEWAY with FALLBACK_GATEWAYS. Both come from environment variables. If an operator sets E2E_GATEWAYS to a list that already contains the trusted gateway, POOL.length exceeds the number of distinct hosts and new Set(seen).size can never match it. The test then reports a round-robin defect that does not exist. Compare against the distinct host count, or deduplicate POOL.

♻️ Proposed change to deduplicate the pool
-const POOL = [TRUSTED_GATEWAY, ...FALLBACK_GATEWAYS];
+const POOL = [...new Set([TRUSTED_GATEWAY, ...FALLBACK_GATEWAYS])];
🤖 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/wayfinder-core/src/e2e/routing.e2e.test.ts` around lines 209 - 225,
Deduplicate the gateway pool before the RoundRobinRoutingStrategy test uses it,
or compare against the pool’s distinct host count instead of POOL.length. Update
the POOL setup and the assertion in the “RoundRobinRoutingStrategy cycles
through the pool” test so duplicate TRUSTED_GATEWAY and FALLBACK_GATEWAYS
entries do not produce an impossible expectation.
packages/wayfinder-core/scripts/verify-package.mjs (2)

56-62: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

On Windows, shell: true can break paths that contain spaces.

execFileSync with shell: true passes each argument through the shell without quoting. The tarball path and workdir come from tmpdir(), which frequently contains a space on Windows, for example C:\Users\Some Name\AppData\Local\Temp. The npm install <tarball> call at Line 88 then receives two arguments. Quote the path arguments, or restrict the script to a POSIX shell and document that.

🤖 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/wayfinder-core/scripts/verify-package.mjs` around lines 56 - 62,
Update the run helper’s Windows execution path to preserve arguments containing
spaces, especially the tarball path and workdir passed to npm install. Quote or
otherwise safely escape path arguments when shell mode is enabled, while keeping
normal argument boundaries intact for other commands.

88-88: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Add a timeout to the install step.

npm install reaches the network. Without timeout, a stalled registry connection blocks the check indefinitely. execFileSync accepts a timeout option, so a single addition in run covers every call.

♻️ Proposed change to bound each child process
-const run = (command, args, cwd) =>
+const run = (command, args, cwd, timeout = 300_000) =>
   execFileSync(command, args, {
     cwd,
     encoding: 'utf8',
     stdio: ['ignore', 'pipe', 'pipe'],
+    timeout,
     shell: process.platform === 'win32',
   });
🤖 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/wayfinder-core/scripts/verify-package.mjs` at line 88, Update the
shared run helper used by verify-package.mjs to pass a finite timeout option to
execFileSync, ensuring the npm install invocation and every other child-process
call cannot hang indefinitely. Preserve the existing command arguments, working
directory, and behavior aside from enforcing the timeout.
packages/wayfinder-core/src/retrieval/chunk.ts (1)

214-231: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider treating a missing chunkTxId alone as an unsupported endpoint.

The guard requires both headers to be absent. If a gateway returns X-Arweave-Chunk-Read-Offset but omits the transaction ID, control reaches the mismatch check at Line 239, which reports Got: null. That is the misleading attribution the comment above aims to avoid.

♻️ Proposed change to classify a missing chunk transaction ID as unsupported
-            if (chunkReadOffsetHeader === null && chunkTxId === null) {
+            if (chunkTxId === null) {
               throw new Error(
                 `Gateway did not return a chunk response at offset ${currentOffset}: ` +
                   `status ${chunkResponse.status} with no ${arioHeaderNames.chunkTxId} or ` +
                   `${arioHeaderNames.chunkReadOffset} headers. The gateway likely does not ` +
                   'support the /chunk/<offset>/data endpoint.',
               );
             }
🤖 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/wayfinder-core/src/retrieval/chunk.ts` around lines 214 - 231,
Update the unsupported-endpoint guard in the chunk response handling to trigger
whenever chunkTxId is null, even if chunkReadOffsetHeader is present. Keep the
existing diagnostic and transaction validation behavior unchanged for responses
that include the transaction ID.
packages/wayfinder-extension/README.md (1)

92-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep documented RPC values tied to the SDK.

packages/wayfinder-extension/src/constants.ts uses MAINNET_RPC_URL and DEVNET_RPC_URL, but this line duplicates their current values. If the SDK changes either constant, the runtime and README will diverge. Refer to the SDK-provided public endpoints instead of hardcoding both URLs.

Proposed wording
-- **Solana RPC URL**: Endpoint for Solana JSON-RPC calls (editable only in Custom mode). Both presets use the public Solana endpoints (`api.mainnet-beta.solana.com` / `api.devnet.solana.com`). The extension reads the gateway registry once every 24 hours from your own IP, which sits well inside those endpoints' per-IP limits — but if you hit rate limiting, or want a dedicated provider, switch to the **Custom** preset and supply your own RPC URL.
+- **Solana RPC URL**: Endpoint for Solana JSON-RPC calls (editable only in Custom mode). Both presets use the SDK-provided public Solana endpoints. The extension reads the gateway registry once every 24 hours from your own IP, which sits well inside those endpoints' per-IP limits — but if you hit rate limiting, or want a dedicated provider, switch to the **Custom** preset and supply your own RPC URL.
🤖 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/wayfinder-extension/README.md` at line 92, Update the Solana RPC URL
documentation in the README to reference the SDK-provided public endpoint
constants, MAINNET_RPC_URL and DEVNET_RPC_URL, rather than hardcoding their
current URL values. Preserve the existing explanation of presets, rate limiting,
and Custom mode.
🤖 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.

Inline comments:
In `@packages/wayfinder-core/package.json`:
- Line 49: Update the test:e2e script in package.json so quoted glob arguments
are compatible with supported Node.js versions: either declare an engines.node
requirement of Node.js 21 or newer, or expand the glob before invoking tsx.
Ensure older Node.js versions do not silently run zero tests.

In `@packages/wayfinder-core/src/e2e/gateways.e2e.test.ts`:
- Around line 114-148: Update joinedGateways() to ensure the ground-truth
registry data is complete: either assert that the response hasMore flag is false
or repeatedly fetch using nextCursor until no cursor remains. Build the
stakeByFqdn and ranked values from all fetched gateways so the ranking
assertions remain valid beyond the 1000-entry page limit.

In `@packages/wayfinder-core/src/e2e/retrieval.e2e.test.ts`:
- Around line 43-46: Update fetchWith to accept a single object parameter
containing dataRetrievalStrategy and optional gateway, then adjust all three
call sites to pass those named properties while preserving the existing default
gateway behavior.

In `@packages/wayfinder-core/src/gateways/network.ts`:
- Around line 86-99: Update the pagination loop in the gateway registry
retrieval method to retry the current page when a request fails, rather than
allowing an undefined cursor or partial results to terminate normally. After the
retry limit is exhausted, propagate the error so the fallback provider can run;
only sort and return the complete collected registry after pagination succeeds.

In `@packages/wayfinder-core/src/gateways/trusted-peers.ts`:
- Around line 48-52: Update the constructor’s retries initialization to validate
that retries is a positive safe integer, preventing Infinity or NaN from
reaching the retry loop; normalize invalid values to the established default
while preserving the minimum retry behavior.

In `@packages/wayfinder-core/src/wayfinder.ts`:
- Around line 382-387: Update the class-level TSDoc describing the default
routing strategy to reflect that the constructor creates PingRoutingStrategy
wrapping RandomRoutingStrategy, replacing the stale FastestPingRoutingStrategy
reference. Keep the documentation aligned with the actual default behavior
configured in the constructor.

In `@packages/wayfinder-extension/src/background.ts`:
- Around line 384-386: Update the migration log in the retired RPC endpoint
handling to remove the raw rpcUrl value from its structured metadata. Replace
retired with a fixed migration identifier or safely redacted origin while
preserving the replacement value and existing migration message.
- Around line 368-389: Update migrateRetiredMainnetRpcUrls to read both rpcUrl
and network from chrome.storage.local before replacing the endpoint. Skip
migration when network is 'custom'; otherwise use AR_IO_SOLANA_DEVNET.rpcUrl for
devnet records and EXTENSION_DEFAULTS.rpcUrl for mainnet records, preserving the
existing exact retired-URL match.
- Around line 389-396: Reorder the migration in the startup flow so the cached
registry keys are removed before setting EXTENSION_DEFAULTS.rpcUrl, ensuring a
remove() failure leaves the retired URL in place and allows the migration to
retry on the next startup. Preserve the existing cache keys and initial-sync
scheduling behavior.

---

Nitpick comments:
In `@packages/wayfinder-core/scripts/verify-package.mjs`:
- Around line 56-62: Update the run helper’s Windows execution path to preserve
arguments containing spaces, especially the tarball path and workdir passed to
npm install. Quote or otherwise safely escape path arguments when shell mode is
enabled, while keeping normal argument boundaries intact for other commands.
- Line 88: Update the shared run helper used by verify-package.mjs to pass a
finite timeout option to execFileSync, ensuring the npm install invocation and
every other child-process call cannot hang indefinitely. Preserve the existing
command arguments, working directory, and behavior aside from enforcing the
timeout.

In `@packages/wayfinder-core/src/e2e/fixtures.ts`:
- Line 93: Update the E2E_TIMEOUT_MS initialization in fixtures.ts to validate
the parsed environment value with a finite-number check, falling back to 90_000
whenever Number(...) produces NaN or another non-finite value; retain valid
numeric timeout values.

In `@packages/wayfinder-core/src/e2e/gateways.e2e.test.ts`:
- Around line 50-58: Update joinedGateways so a rejected registrySnapshot
promise clears registrySnapshot before propagating the error, allowing
subsequent calls to perform a fresh withRpcRetry request while preserving
successful promise caching.
- Around line 50-58: Replace the untyped gateway records in joinedGateways and
the gateway filter callbacks at lines 118 and 124 of
packages/wayfinder-core/src/e2e/gateways.e2e.test.ts with the SDK gateway type
exported by `@ar.io/sdk`, removing the any annotations. In
packages/wayfinder-core/src/e2e/fixtures.ts lines 133-135, catch errors as
unknown and narrow them before accessing context.statusCode or message.

In `@packages/wayfinder-core/src/e2e/routing.e2e.test.ts`:
- Around line 209-225: Deduplicate the gateway pool before the
RoundRobinRoutingStrategy test uses it, or compare against the pool’s distinct
host count instead of POOL.length. Update the POOL setup and the assertion in
the “RoundRobinRoutingStrategy cycles through the pool” test so duplicate
TRUSTED_GATEWAY and FALLBACK_GATEWAYS entries do not produce an impossible
expectation.

In `@packages/wayfinder-core/src/e2e/verification.e2e.test.ts`:
- Around line 96-107: Update gatewayServingTx so every fetch probe cancels or
drains res.body immediately after evaluating res.ok, including unsuccessful
responses, before returning the status result. Preserve the existing gateway
selection behavior and timeout handling.
- Around line 184-209: Remove the unhandled-rejection workaround surrounding the
verify call: delete the leaked collection, captureLeak handler, process listener
registration/removal, and related comments. Keep the existing verify invocation
and result assertion in the test unchanged.

In `@packages/wayfinder-core/src/gateways/network.ts`:
- Around line 59-72: Update sortGateways and its sortValue helper to use the SDK
gateway type or a local structural interface instead of any and any[]. Type the
nested sort-property access safely while preserving the existing numeric
conversion and ascending/descending ordering behavior.

In `@packages/wayfinder-core/src/retrieval/chunk.ts`:
- Around line 214-231: Update the unsupported-endpoint guard in the chunk
response handling to trigger whenever chunkTxId is null, even if
chunkReadOffsetHeader is present. Keep the existing diagnostic and transaction
validation behavior unchanged for responses that include the transaction ID.

In `@packages/wayfinder-extension/README.md`:
- Line 92: Update the Solana RPC URL documentation in the README to reference
the SDK-provided public endpoint constants, MAINNET_RPC_URL and DEVNET_RPC_URL,
rather than hardcoding their current URL values. Preserve the existing
explanation of presets, rate limiting, and Custom mode.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 52803eab-0c9b-404d-a9b3-306a532cb15b

📥 Commits

Reviewing files that changed from the base of the PR and between df089bb and 9dddb5d.

📒 Files selected for processing (30)
  • CLAUDE.md
  • README.md
  • packages/wayfinder-core/CHANGELOG.md
  • packages/wayfinder-core/README.md
  • packages/wayfinder-core/package.json
  • packages/wayfinder-core/scripts/verify-package.mjs
  • packages/wayfinder-core/src/e2e/README.md
  • packages/wayfinder-core/src/e2e/fixtures.ts
  • packages/wayfinder-core/src/e2e/gateways.e2e.test.ts
  • packages/wayfinder-core/src/e2e/retrieval.e2e.test.ts
  • packages/wayfinder-core/src/e2e/routing.e2e.test.ts
  • packages/wayfinder-core/src/e2e/verification.e2e.test.ts
  • packages/wayfinder-core/src/gateways/network.ts
  • packages/wayfinder-core/src/gateways/trusted-peers.ts
  • packages/wayfinder-core/src/retrieval/chunk.ts
  • packages/wayfinder-core/src/routing/ping.test.ts
  • packages/wayfinder-core/src/routing/ping.ts
  • packages/wayfinder-core/src/routing/preferred-with-fallback.test.ts
  • packages/wayfinder-core/src/utils/verify-stream.ts
  • packages/wayfinder-core/src/wayfinder.test.ts
  • packages/wayfinder-core/src/wayfinder.ts
  • packages/wayfinder-core/tsconfig.json
  • packages/wayfinder-extension/CHANGELOG.md
  • packages/wayfinder-extension/CLAUDE.md
  • packages/wayfinder-extension/README.md
  • packages/wayfinder-extension/package.json
  • packages/wayfinder-extension/src/background.ts
  • packages/wayfinder-extension/src/constants.ts
  • packages/wayfinder-react/CHANGELOG.md
  • packages/wayfinder-react/package.json

Comment thread packages/wayfinder-core/package.json
Comment thread packages/wayfinder-core/src/e2e/gateways.e2e.test.ts
Comment thread packages/wayfinder-core/src/e2e/retrieval.e2e.test.ts Outdated
Comment thread packages/wayfinder-core/src/gateways/network.ts
Comment thread packages/wayfinder-core/src/gateways/trusted-peers.ts Outdated
Comment thread packages/wayfinder-core/src/wayfinder.ts
Comment thread packages/wayfinder-extension/src/background.ts Outdated
Comment thread packages/wayfinder-extension/src/background.ts Outdated
Comment thread packages/wayfinder-extension/src/background.ts Outdated
vilenarios and others added 3 commits August 12, 2026 15:35
Review fixes:
- NetworkGatewaysProvider no longer returns a partial registry. A failed page
  left `cursor` undefined, so a single first-page failure exited the loop and
  returned an empty list with no retry; a later failure returned earlier pages
  sorted as if complete. Since `limit` is applied after sorting, that returned
  the wrong gateways rather than merely fewer. Retries the failed page, then
  throws so a composite provider can fall through.
- TrustedPeersGatewaysProvider validates `retries`. `Math.max(1, n)` passed
  `NaN` (skipping every attempt) and `Infinity` (retrying forever) straight
  through.
- The extension RPC migration reads `network` first: it skips the `custom`
  preset instead of overwriting a user's setting, and uses the devnet default
  on devnet rather than writing a mainnet RPC onto the wrong cluster. It clears
  the cached registry before rewriting `rpcUrl`, so a crash between the two
  writes re-runs the migration instead of stranding a stale registry. Logs only
  the retired endpoint's origin, since the URL embeds an access token.
- Corrected the Wayfinder TSDoc claiming FastestPingRoutingStrategy is the
  default, made the e2e ground-truth snapshot page to the end of the registry,
  and converted `fetchWith` to an object parameter per the coding guidelines.

Beyond the review: peer discovery had no fallback, and turbo-gateway.com's
/ar-io/peers was returning an empty list on every attempt while this was being
written — which left both default clients unable to serve any request.
createWayfinderClient() and new Wayfinder() now default to a composite that
falls back to the trusted gateway itself, so that outage degrades routing
instead of stopping it. Verified against the live failure: 20/20 and 10/10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y
Adds an explicit matrix of what the live suite proves and what it does not, so
the scope is a checked-in statement rather than an assumption. Records the
untested surfaces (events end-to-end, telemetry, browser/localStorage, x402)
and the packages with no suite at all — wayfinder-react, the extension, and the
CLI — which are the largest coverage gaps in the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y
Wayfinder injected its default gateways provider into any strategy lacking one,
including wrappers such as PingRoutingStrategy. A wrapper resolves a candidate
list and passes it down, and RandomRoutingStrategy prefers a supplied list over
its own provider — so the injection silently replaced whichever provider the
caller had configured on the inner strategy.

The Chrome extension hit this in its 'Balanced (Random)' routing mode: gateways
came from the default peer list instead of ChromeStorageGatewayProvider, so the
user's blacklist and the joined/failed-epoch filtering were bypassed and a
blacklisted gateway could still be selected. Reproduced against the extension's
exact wiring (provider consulted 0 times before, 5 after).

Injection is now limited to strategies that select gateways themselves; nested
strategies are still reached by the existing recursion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y
@vilenarios

Copy link
Copy Markdown
Contributor Author

Consumer audit: are the extension, CLI and React using core correctly?

Traced each package's actual wiring rather than assuming. One real bug, one wasted call, one React smell.

🐞 Extension random mode bypassed the gateway blacklist

ChromeStorageGatewayProvider was never consulted in the "Balanced (Random)" routing mode. Reproduced against the extension's exact wiring:

mode gateway chosen ChromeStorageGatewayProvider calls
topStaked (default) extension's list 1 ✅
random turbo-gateway peer list 0 ❌

Root cause is in core, not the extension. Wayfinder injects its default provider into any strategy lacking one — including the PingRoutingStrategy wrapper. The wrapper then resolves that list and passes it down, and RandomRoutingStrategy prefers a supplied list over its own provider. So the extension's blacklist, status === 'joined' filter and failed-epoch filter were all silently discarded, and a blacklisted gateway could still be routed to.

Fixed in 6bf210e: injection is now limited to strategies that select gateways themselves; nested strategies are still reached by the recursion. After the fix the provider is consulted on every attempt (0 → 5). Regression test added.

Worth noting the two inner strategies disagree by design: RoundRobinRoutingStrategy.selectGateway() takes no arguments and ignores a supplied list, which is the only reason topStaked — the extension's default — escaped this.

⚠️ Extension topStaked makes a wasted request

The ping wrapper still fetches turbo-gateway's ~78KB peer list on every selection, then discards it because RoundRobin ignores the argument. Harmless but pointless; the extension could pass its provider at the Wayfinder level instead of relying on injection.

⚠️ React recreates the client on every render

wayfinder-provider.tsx memoises on [options], an object prop. Unless the caller memoises it, the identity changes each render and a new Wayfinder is constructed every time — discarding routing state and re-fetching gateways. Not changed here: the React package has zero tests, so I can't verify a fix, and it's outside this PR's scope.

✅ CLI

Uses createWayfinderClient with createRoutingStrategy, which builds leaf strategies directly — no wrapper-over-provider nesting, so it was never affected. It also picks up the new resilient default provider automatically.


Also verified in this round: new Wayfinder() 62/62 integration, 132/132 unit after the change.

@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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/wayfinder-core/src/gateways/network.ts (1)

62-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace any in the new sorter.

Use the SDK gateway item type for sortGateways and sortValue. Type the dotted accessor against the supported SortBy paths.

🤖 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/wayfinder-core/src/gateways/network.ts` around lines 62 - 69, Update
sortGateways and its nested sortValue in the network gateway class to use the
SDK gateway item type instead of any. Type the dotted property accessor using
the supported SortBy paths, while preserving the existing numeric conversion and
non-finite fallback behavior.

Source: Coding guidelines

🤖 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.

Inline comments:
In `@packages/wayfinder-core/src/e2e/README.md`:
- Line 61: Update the “Retrieve via chunks” entry in the e2e README from
successful to unavailable or unverified, matching the documented failure status
for live chunk tests. Keep the existing test reference and failure details
intact.

---

Nitpick comments:
In `@packages/wayfinder-core/src/gateways/network.ts`:
- Around line 62-69: Update sortGateways and its nested sortValue in the network
gateway class to use the SDK gateway item type instead of any. Type the dotted
property accessor using the supported SortBy paths, while preserving the
existing numeric conversion and non-finite fallback behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: da96bf9e-5c04-4a0d-ae95-715c3102ec2c

📥 Commits

Reviewing files that changed from the base of the PR and between 9dddb5d and 6bf210e.

📒 Files selected for processing (13)
  • packages/wayfinder-core/CHANGELOG.md
  • packages/wayfinder-core/README.md
  • packages/wayfinder-core/src/client.ts
  • packages/wayfinder-core/src/e2e/README.md
  • packages/wayfinder-core/src/e2e/gateways.e2e.test.ts
  • packages/wayfinder-core/src/e2e/retrieval.e2e.test.ts
  • packages/wayfinder-core/src/gateways/default.ts
  • packages/wayfinder-core/src/gateways/network.ts
  • packages/wayfinder-core/src/gateways/trusted-peers.ts
  • packages/wayfinder-core/src/index.ts
  • packages/wayfinder-core/src/wayfinder.test.ts
  • packages/wayfinder-core/src/wayfinder.ts
  • packages/wayfinder-extension/src/background.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/wayfinder-core/CHANGELOG.md
  • packages/wayfinder-core/src/e2e/gateways.e2e.test.ts
  • packages/wayfinder-core/README.md
  • packages/wayfinder-core/src/gateways/trusted-peers.ts
  • packages/wayfinder-extension/src/background.ts

Comment thread packages/wayfinder-core/src/e2e/README.md Outdated
RoundRobinRoutingStrategy loaded its gateway list once and cycled it for the
life of the instance. That made any SimpleCacheGatewaysProvider or
LocalStorageGatewaysProvider TTL wrapped around it meaningless, and a list that
narrows was never picked up — in the Chrome extension that is why blacklisting a
gateway had no effect until the service worker restarted (gateways.ts writes the
blacklist without resetting the instance). It now asks the provider per
selection, leaving caching to the provider layer where it belongs, and honours a
caller-supplied list when provider-backed as the RoutingStrategy interface
advertises. A list pinned at construction still wins — that is long-standing
behaviour asserted by existing tests, so the change is scoped to the
provider-backed case.

WayfinderProvider collects props with a rest spread, so `options` was a new
object on every render and the useMemo keyed on it never memoised anything: a
fresh Wayfinder, gateways provider, emitter and telemetry setup were built on
every render. It now depends on the individual options and memoises the context
value. The gateways provider is also always wrapped in
LocalStorageGatewaysProvider, including the default one — previously an app that
passed no provider (or passed routingSettings) got no gateway caching at all in
the browser.

The extension's wasted peer-list fetch needed no separate fix: limiting provider
injection to non-wrapper strategies already removed it (verified, 0 fetches).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y
@vilenarios

Copy link
Copy Markdown
Contributor Author

Follow-up: all three consumer issues resolved (383f370)

Verified each before changing anything — one needed no fix at all.

✅ Extension wasted peer fetch — already fixed, no change needed

Limiting provider injection to non-wrapper strategies (6bf210e) had already removed it. Measured by counting outbound /ar-io/peers requests through the extension's exact wiring:

selected=permagate.io  chromeProviderCalls=1  peerListFetches=0
wrapper.gatewaysProvider = undefined (delegates)

🐞 RoundRobinRoutingStrategy froze its gateway list — real, user-visible

Worse than the interface mismatch I first reported. It loaded the list once and cycled it for the life of the instance, so:

  • any SimpleCacheGatewaysProvider / LocalStorageGatewaysProvider TTL wrapped around it was meaningless — it never asked again;
  • a list that narrows was never picked up.

That second one bites users directly: gateways.ts:596 writes blacklistedGateways to storage but never resets the Wayfinder instance, and topStaked (the extension default) is Ping(RoundRobin(chromeStorage)). So blacklisting a gateway had no effect until the service worker restarted.

Now asks the provider per selection — caching belongs to the provider layer — and honours a caller-supplied list when provider-backed.

A note on scope: two existing tests (uses the internal list even when a different list is provided, handles a single gateway by returning it repeatedly) explicitly assert that a list pinned at construction beats a supplied one. My first attempt made params.gateways win unconditionally and broke both. That precedence is a deliberate decision, so I narrowed the change to the provider-backed case and left the pinned-list semantics intact. Both original tests pass unmodified; three new ones cover the contract.

🐞 React rebuilt the client on every render — worse than reported

WayfinderProvider collects props with a rest spread ({ children, ...options }), so options is a new object on every render unconditionally — the useMemo keyed on it never memoised anything. A fresh Wayfinder, gateways provider, emitter and telemetry setup were constructed on every render, not just when a caller forgot to memoise.

Now depends on the individual options and memoises the context value so consumers stop re-rendering with their parent.

While in there: the provider only wrapped a caller-supplied gateways provider in LocalStorageGatewaysProvider. An app that passed none — or that passed routingSettings, which makes createWayfinderClient skip its cached default — got no gateway caching at all in the browser. The default source is now wrapped too.


unit 135/135 · integration 62/62 · e2e 38/40 · packaging passes · biome clean (92 files)

The 2 e2e failures remain the gateway-side chunk tests, left as-is per direction.

Building a React test harness immediately caught a regression in the previous
commit: always wrapping the gateways provider in LocalStorageGatewaysProvider
throws outside a browser, so server-side rendering would crash on any app using
WayfinderProvider. The browser/node branch that createWayfinderClient kept
privately is now exported from core as createCachedGatewaysProvider(), and both
callers use it — localStorage in the browser, an in-memory cache elsewhere.

wayfinder-react gains a suite (react-test-renderer, no DOM required) covering the
memoisation fix, the SSR path, and that a caller-supplied provider survives.
Its tsconfig previously had no exclude, so test files would have shipped in dist.

RoundRobinRoutingStrategy now resumes from the gateway it last served rather than
from a numeric index. Re-reading the provider per selection means the pool can
change between calls, and an index into a list that shifted points somewhere
arbitrary — measured before the fix, a gateway was skipped and another re-served
early the moment one dropped out of the list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@packages/wayfinder-react/src/components/wayfinder-provider.test.tsx`:
- Around line 25-29: In
packages/wayfinder-react/src/components/wayfinder-provider.test.tsx, lines
25-29, sort the imports according to Biome’s ordering rules. Also format the
type assertion at lines 109-111 with Biome’s expected syntax; no other behavior
changes are needed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a948fe77-2ef5-4be5-af38-b6184202acd1

📥 Commits

Reviewing files that changed from the base of the PR and between 6bf210e and 899a443.

📒 Files selected for processing (13)
  • README.md
  • packages/wayfinder-core/CHANGELOG.md
  • packages/wayfinder-core/src/client.ts
  • packages/wayfinder-core/src/e2e/README.md
  • packages/wayfinder-core/src/gateways/default.ts
  • packages/wayfinder-core/src/routing/round-robin.test.ts
  • packages/wayfinder-core/src/routing/round-robin.ts
  • packages/wayfinder-react/CHANGELOG.md
  • packages/wayfinder-react/LICENSE
  • packages/wayfinder-react/package.json
  • packages/wayfinder-react/src/components/wayfinder-provider.test.tsx
  • packages/wayfinder-react/src/components/wayfinder-provider.tsx
  • packages/wayfinder-react/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.md
  • packages/wayfinder-core/src/client.ts

Comment thread packages/wayfinder-react/src/components/wayfinder-provider.test.tsx Outdated
vilenarios and others added 2 commits August 12, 2026 20:46
The react lint job failed on import ordering and formatting in the new test
file. Reproduced with the exact command CI runs (from the package directory,
against ../../biome.json) rather than from the repo root, which is what let it
through locally.

Also reverts packages/wayfinder-react/LICENSE, which was an uncommitted MIT ->
Apache-2.0 change already present in the working tree and got swept into an
earlier commit by a directory-wide `git add`. Relicensing is a deliberate
decision that shouldn't ride along in a bug-fix PR, and it currently disagrees
with package.json, which still declares MIT. The edit is left in the working
tree untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y
Chunk retrieval was marked as covered and passing while the notes below said the
live test fails on every gateway tested. Adds a third state so the table cannot
contradict itself: covered and passing, covered but failing for reasons outside
the library, and not verified at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153F31T8zBhNtJgrZPwzK1y
@vilenarios
vilenarios merged commit ac695f8 into main Aug 13, 2026
10 checks passed
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.

1 participant