fix: make OpenRouter debug-fetch patch concurrency-safe - #416
Open
Greg Land (bikeusaland) wants to merge 9 commits into
Open
fix: make OpenRouter debug-fetch patch concurrency-safe#416Greg Land (bikeusaland) wants to merge 9 commits into
Greg Land (bikeusaland) wants to merge 9 commits into
Conversation
installOpenRouterDebugFetch swapped globalThis.fetch for the whole run and restored it blindly in a finally. Two overlapping runOpenWikiAgent calls (subagents, parallel tests, a long-lived host) corrupted each other: the second install captured the first's patched fetch as its "original", and the first restore clobbered the second's patch — so the wrapper leaked permanently or was lost, and both runs shared a single failure slot. ChatOpenRouter extends BaseChatModel and calls globalThis.fetch directly with no injectable fetch (unlike the OpenAI/Codex/Vertex clients, which take a configuration.fetch hook), so a global wrapper is the only interception point. Make that wrapper reference-counted: install it once against the genuine original fetch, register a per-run failure sink so concurrent runs never share or clobber each other's captured failure (debug events still route to each run's own options), and restore the global only when the last run detaches. Redundant restore() calls are idempotent. Exported for testing; new coverage pins the concurrency contract. Fixes langchain-ai#411 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a concurrency hazard where the OpenRouter debug-fetch patch could leak permanently or be lost when two OpenWiki runs overlap in the same process (#411).
installOpenRouterDebugFetchswappedglobalThis.fetchfor the duration of a run and restored it blindly in afinally. With two overlappingrunOpenWikiAgentcalls — subagents, parallel tests, or a long-lived host process — the install/restore steps corrupted each other:fetchas its original.fetch.Both runs also shared a single failure slot, so one run's captured error could overwrite another's.
Why not a per-model
configuration.fetchhookThe issue suggested attaching diagnostics per-model like the Codex/Vertex clients do. That isn't available here:
ChatOpenRouter(from@langchain/openrouter) extendsBaseChatModeland callsglobalThis.fetchdirectly — its constructor exposes nofetch/configurationfield. A global wrapper is genuinely the only interception point, which is why the original code patched the global.Fix
Make the global wrapper reference-counted:
fetch.getLastFailure/clearLastFailureoperate on the calling run's own sink, and debug events route to each run's ownonEvent.restore()calls on the same handle are idempotent (can't prematurely restore while another run is active).The
OpenRouterFetchCapturereturn contract (getLastFailure,clearLastFailure,restore) is unchanged, so callers are untouched.Tests
New
test/openrouter-debug-fetch.test.ts(4 tests): single-run restore returns the exact original; overlapping runs each capture their own 429 while restore stays reference-counted; non-OpenRouter requests pass through untouched; redundant restore is a no-op that doesn't disturb an active run.Full suite green: typecheck,
eslint .,prettier --check, and all tests pass.Closes #411
🤖 Generated with Claude Code