Skip to content

fix(middleware): negotiate loopback debugger origins - #465

Closed
avocado-cb wants to merge 1 commit into
callstackincubator:mainfrom
avocado-cb:fix/loopback-debugger-origin
Closed

fix(middleware): negotiate loopback debugger origins#465
avocado-cb wants to merge 1 commit into
callstackincubator:mainfrom
avocado-cb:fix/loopback-debugger-origin

Conversation

@avocado-cb

@avocado-cb avocado-cb commented Aug 25, 2026

Copy link
Copy Markdown

Description

Negotiate the debugger WebSocket Origin for loopback Metro sessions instead of unconditionally rewriting numeric loopback addresses to localhost.

  • Try the Origin matching webSocketDebuggerUrl first.
  • Retry loopback connections with the alternate localhost or 127.0.0.1 Origin.
  • Keep retry eligibility open until the initial ReactNativeApplication.enable and Runtime.enable CDP commands succeed, because an Expo Origin mismatch is terminated after the WebSocket upgrade.
  • Preserve remote and secure debugger Origins without fallback.

Related Issue

Closes #463

Context

The React Native and Expo layers validate different stages of this connection:

  • React Native dev-middleware requires an Origin during the WebSocket upgrade and rejects a missing header with HTTP 401. It accepts common loopback hostnames.
  • Expo CLI performs a second exact-host check after the upgrade. If the Origin host differs from Expo's configured serverBaseUrl, Expo terminates the socket and the client sees close code 1006 even though the WebSocket emitted open.

Expo's configured hostname is environment-dependent. REACT_NATIVE_PACKAGER_HOSTNAME=localhost makes it expect localhost; with that variable unset, Expo's URL creator normalizes its localhost host type to 127.0.0.1. Meanwhile, /json/list builds webSocketDebuggerUrl from the discovery request's Host header. The target URL therefore does not always identify the Origin Expo will accept.

That explains why a localhost-only rewrite can fix one Metro process while breaking another. The fallback must work in both directions and must cover post-upgrade termination, not only HTTP handshake rejection.

Testing

  • pnpm release:plan
  • pnpm checks:affected
  • pnpm test:affected — 173 middleware tests passed; all affected tasks passed
  • pnpm --config.verify-deps-before-run=warn --filter @rozenite/middleware exec vitest --run src/__tests__/agent-session.test.ts — 31 tests passed
  • Started the playground Metro server; all 14 plugins loaded and Metro reached Waiting on http://localhost:8082
  • Registered a synthetic inspector page and verified both Expo configurations:
    • REACT_NATIVE_PACKAGER_HOSTNAME=localhost: numeric Origin upgraded and then closed with 1006; localhost remained connected.
    • variable unset: numeric Origin remained connected; localhost upgraded and then closed with 1006.
    • missing Origin was rejected with HTTP 401.

Co-authored-by: Codex <codex-noreply@coinbase.com>
@avocado-cb
avocado-cb force-pushed the fix/loopback-debugger-origin branch from c20a6d2 to d3b56b8 Compare August 26, 2026 09:07
@avocado-cb avocado-cb changed the title fix(middleware): normalize loopback debugger origins fix(middleware): negotiate loopback debugger origins Aug 26, 2026
@V3RON

V3RON commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Thanks for contribution. While going through your implementation an alternative solution came to my mind, which doesn't require any retries or hardcoding URLs.

#468

@V3RON V3RON closed this Aug 31, 2026
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.

Agent session fails for numeric loopback debugger origins

2 participants