[GH-ISSUE #7681] [Bug] Session redirect rows omit the promised prior-hop response context #4603

Closed
opened 2026-10-31 00:51:13 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 28, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7681

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

I ran node scripts/rea.mjs observe-web-session <loopback-CDP-endpoint> allowed-page --observation-ms 20 --json against the repository's fake CDP server. The server reported a main-frame GET followed by a same-origin redirect under the same request ID, with the prior URL and HTTP status 302 supplied in the redirect response. Both request timestamps followed the sampled observation boundary.

The command succeeded, but its redirect timeline row retained only the next URL. Its detail was null, and neither the prior-hop URL nor status 302 appeared anywhere in the complete JSON output. This reproduced in all four CLI invocations across two runs of the driver. No real browser or GUI was used.

Reproduction

  1. Use the baseline below, its installed locked dependencies and already-built runtime, with Node.js v24.22.1. Run the following from the repository root. The package-runner fixtures prevent incidental npm/npx/bun use; HOME and XDG state are isolated. The inline driver imports the existing loopback CDP fixture and invokes the public CLI twice.
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
mkdir -p "$work"/{home,config,cache,data,state,tmp,bin}
for runner in npm npx bun; do
  printf '#!/bin/sh
exit 97
' > "$work/bin/$runner"
  chmod +x "$work/bin/$runner"
done
export PATH="$work/bin:$PATH"
export HOME="$work/home" XDG_CONFIG_HOME="$work/config"
export XDG_CACHE_HOME="$work/cache" XDG_DATA_HOME="$work/data"
export XDG_STATE_HOME="$work/state" TMPDIR="$work/tmp"
node --import tsx --input-type=module <<'JS'
import { spawn } from 'node:child_process';
import { pathToFileURL } from 'node:url';
const { startFakeCdpBrowser } = await import(pathToFileURL(`${process.cwd()}/tests/fixtures/fakeCdpBrowser.ts`).href);
const browser = await startFakeCdpBrowser({
  eventsBeforeReply: ['Performance.getMetrics'],
  commandResult: c => c.method === 'Performance.enable' ? {} : c.method === 'Performance.getMetrics' ? { metrics: [{name:'Timestamp',value:20}] } : undefined,
  commandEvents: (c, origin) => c.method === 'Performance.getMetrics' ? [
    {method:'Network.requestWillBeSent',sessionId:c.sessionId,params:{requestId:'redirect-chain',timestamp:20.5,type:'Document',frameId:'frame-main',request:{url:`${origin}/prior-hop`,method:'GET'}}},
    {method:'Network.requestWillBeSent',sessionId:c.sessionId,params:{requestId:'redirect-chain',timestamp:21,type:'Document',frameId:'frame-main',redirectResponse:{url:`${origin}/prior-hop`,status:302,mimeType:'text/html',encodedDataLength:0,headers:{location:`${origin}/next-hop`}},request:{url:`${origin}/next-hop`,method:'GET'}}}
  ] : undefined,
});
const run = args => new Promise((resolve,reject) => {
 const child=spawn(process.execPath,['scripts/rea.mjs',...args],{env:process.env});
 let out='',err=''; child.stdout.on('data',d=>out+=d);child.stderr.on('data',d=>err+=d);child.on('error',reject);child.on('close',code=>resolve({code,out,err}));
});
try {
 for (let n=1;n<=2;n++) {
 const result=await run(['observe-web-session',browser.endpoint,'allowed-page','--observation-ms','20','--json']);
 const value=JSON.parse(result.out).normalized_result;
 console.log(JSON.stringify({run:n,exit:result.code,result:value,priorHopAnywhere:result.out.includes('/prior-hop'),status302Anywhere:result.out.includes('302'),stderr:result.err}));
 }
 console.log(JSON.stringify({reaVersion:await run(['--version']),node:process.version,platform:process.platform,arch:process.arch,connectionsBeforeClose:browser.activeConnectionCount()}));
} finally {await browser.close();}
JS
  1. Inspect each printed result's timeline. Each has one redirect row for /next-hop, with no prior response fields and detail: null. The diagnostic booleans are false. The version check prints 6.8.0 and the fixture reports zero active connections after the CLI calls. The driver closes the fixture in finally; the shell trap removes the isolated state.

All four CLI invocations exited 0 with empty stderr and window.end_reason: "window_elapsed".

Expected behavior

A redirect admitted into the observation window should retain its paired prior-hop response context, including the supplied prior URL and response status. This expectation concerns navigation response metadata; response headers and bodies remain excluded.

Impact and scope

P2: callers lose redirect provenance from an otherwise successful session timeline. The returned row establishes a destination but does not identify the preceding hop or its supplied response status, preventing reconstruction of those facts from this result. All four captures reported zero dropped timeline events, no excluded or unavailable entries, and only the ordinary attach limitation.

The demonstrated scope is a synthetic, approved-origin main-frame Document redirect through the public CLI on the specified baseline. Real-engine frequency, other resource types, other platforms and MCP behavior were not tested. An equivalent workaround preserving this session result was not demonstrated. No severe harm or security-boundary crossing was observed.

REA version

Exact node scripts/rea.mjs --version stdout:

6.8.0

Source baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1; Darwin arm64. The protocol provider was the repository's synthetic FakeChrome/1.0 fixture, not an installed browser engine.

Target and artifact context

An HTTP/WebSocket CDP fixture bound to 127.0.0.1, exposing the allowed-page target. It reports a browser Timestamp of 20, a prior request at 20.5 and its redirect continuation at 21. Both hops use the approved loopback origin. The artifact inspected was the complete CLI JSON result and its normalized session timeline.

Interface

CLI

Earliest failure stage

Capture or observation

Relevant output or evidence

The redirect row from each invocation had the following values, with the ephemeral fixture port shown as PORT:

{
  "sequence": 1,
  "type": "redirect",
  "timestamp": 21,
  "frame_id": "frame-main",
  "loader_id": null,
  "request_id": "redirect-chain",
  "url": "http://127.0.0.1:PORT/next-hop",
  "destination_scope": "approved",
  "detail": null
}

All four output documents lacked /prior-hop and 302; completeness.status was attach_limited, excluded and unavailable_sections were empty, and dropped_events.timeline_events was 0.

Expected source at the baseline: docs/browser-observation.md:401-403 says, “Network redirect events retain their paired prior-hop response context when the timestamped next-request event falls inside the window.” This is the session timeline paragraph. The separate passive-inspection contract at docs/browser-observation.md:454 describes prior-hop URLs and response status among retained redirect metadata; it does not define the exact session row schema. I also read docs/web-runtime.md: it documents execution and listener inspection and contains no redirect-row promise.

Failure source: src/browser/CdpNetworkEventFacts.ts:31-48 parses response URL and status, and src/browser/CdpObservationSession.ts:501-530 admits this redirect but emits the next request URL with null detail. src/domain/browserSession.ts:24-55 defines the timeline row without a response-context field. The supplied built producer has the corresponding behavior.

The existing redirect assertion at tests/boundary/browser/cdpObservationWindowBuffer.test.ts:196-203 checks type, request ID, timestamp and destination URL only. Scope controls at tests/boundary/browser/cdpObservationWindowScope.test.ts:254-260 and :435-439 do not require prior response context. These tests were inspected. The attempted npm run test:local -- tests/boundary/browser/cdpObservationWindowBuffer.test.ts tests/boundary/browser/cdpObservationWindowScope.test.ts exited before executing tests, directing the caller to use test:focused for built projects. No test-suite pass is claimed.

Analysis or suspected cause

Source-derived: the session redirect handler checks that the shared parsed redirect has a response, then constructs a destination-only timeline event. The response facts remain unused by that projection, and the public row schema has no dedicated response-context field. This agrees with the missing values in the executed CLI results; no implementation change was attempted.

Additional context

Read-only duplicate searches covered open and closed issues, merged and unmerged PRs, and issue comments, using redirect/session/timeline/context/prior-hop terms. No matching report was found. Related reports concern separate contracts: #7410 loses Location for browser-scenario request steps; #3172 handles nullable redirect fields in offline comparisons; #2593 handles final URL after scope exit; #5609 handles historical lifecycle events; #4392 handles timer overflow. The closed lifecycle issue does not claim to fix the response-context omission reproduced here. Merged PR #986 retains hops in passive page inspection, and closed PR #2045 proposes opt-in session HAR recording; neither supplies the documented session timeline context in this baseline.

Originally created by @coygeek on GitHub (Oct 28, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7681 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior I ran `node scripts/rea.mjs observe-web-session <loopback-CDP-endpoint> allowed-page --observation-ms 20 --json` against the repository's fake CDP server. The server reported a main-frame GET followed by a same-origin redirect under the same request ID, with the prior URL and HTTP status 302 supplied in the redirect response. Both request timestamps followed the sampled observation boundary. The command succeeded, but its redirect timeline row retained only the next URL. Its `detail` was null, and neither the prior-hop URL nor status 302 appeared anywhere in the complete JSON output. This reproduced in all four CLI invocations across two runs of the driver. No real browser or GUI was used. ### Reproduction 1. Use the baseline below, its installed locked dependencies and already-built runtime, with Node.js v24.22.1. Run the following from the repository root. The package-runner fixtures prevent incidental npm/npx/bun use; HOME and XDG state are isolated. The inline driver imports the existing loopback CDP fixture and invokes the public CLI twice. ```sh work=$(mktemp -d) trap 'rm -rf "$work"' EXIT mkdir -p "$work"/{home,config,cache,data,state,tmp,bin} for runner in npm npx bun; do printf '#!/bin/sh exit 97 ' > "$work/bin/$runner" chmod +x "$work/bin/$runner" done export PATH="$work/bin:$PATH" export HOME="$work/home" XDG_CONFIG_HOME="$work/config" export XDG_CACHE_HOME="$work/cache" XDG_DATA_HOME="$work/data" export XDG_STATE_HOME="$work/state" TMPDIR="$work/tmp" node --import tsx --input-type=module <<'JS' import { spawn } from 'node:child_process'; import { pathToFileURL } from 'node:url'; const { startFakeCdpBrowser } = await import(pathToFileURL(`${process.cwd()}/tests/fixtures/fakeCdpBrowser.ts`).href); const browser = await startFakeCdpBrowser({ eventsBeforeReply: ['Performance.getMetrics'], commandResult: c => c.method === 'Performance.enable' ? {} : c.method === 'Performance.getMetrics' ? { metrics: [{name:'Timestamp',value:20}] } : undefined, commandEvents: (c, origin) => c.method === 'Performance.getMetrics' ? [ {method:'Network.requestWillBeSent',sessionId:c.sessionId,params:{requestId:'redirect-chain',timestamp:20.5,type:'Document',frameId:'frame-main',request:{url:`${origin}/prior-hop`,method:'GET'}}}, {method:'Network.requestWillBeSent',sessionId:c.sessionId,params:{requestId:'redirect-chain',timestamp:21,type:'Document',frameId:'frame-main',redirectResponse:{url:`${origin}/prior-hop`,status:302,mimeType:'text/html',encodedDataLength:0,headers:{location:`${origin}/next-hop`}},request:{url:`${origin}/next-hop`,method:'GET'}}} ] : undefined, }); const run = args => new Promise((resolve,reject) => { const child=spawn(process.execPath,['scripts/rea.mjs',...args],{env:process.env}); let out='',err=''; child.stdout.on('data',d=>out+=d);child.stderr.on('data',d=>err+=d);child.on('error',reject);child.on('close',code=>resolve({code,out,err})); }); try { for (let n=1;n<=2;n++) { const result=await run(['observe-web-session',browser.endpoint,'allowed-page','--observation-ms','20','--json']); const value=JSON.parse(result.out).normalized_result; console.log(JSON.stringify({run:n,exit:result.code,result:value,priorHopAnywhere:result.out.includes('/prior-hop'),status302Anywhere:result.out.includes('302'),stderr:result.err})); } console.log(JSON.stringify({reaVersion:await run(['--version']),node:process.version,platform:process.platform,arch:process.arch,connectionsBeforeClose:browser.activeConnectionCount()})); } finally {await browser.close();} JS ``` 2. Inspect each printed result's `timeline`. Each has one redirect row for `/next-hop`, with no prior response fields and `detail: null`. The diagnostic booleans are false. The version check prints `6.8.0` and the fixture reports zero active connections after the CLI calls. The driver closes the fixture in `finally`; the shell trap removes the isolated state. All four CLI invocations exited 0 with empty stderr and `window.end_reason: "window_elapsed"`. ### Expected behavior A redirect admitted into the observation window should retain its paired prior-hop response context, including the supplied prior URL and response status. This expectation concerns navigation response metadata; response headers and bodies remain excluded. ### Impact and scope P2: callers lose redirect provenance from an otherwise successful session timeline. The returned row establishes a destination but does not identify the preceding hop or its supplied response status, preventing reconstruction of those facts from this result. All four captures reported zero dropped timeline events, no excluded or unavailable entries, and only the ordinary attach limitation. The demonstrated scope is a synthetic, approved-origin main-frame Document redirect through the public CLI on the specified baseline. Real-engine frequency, other resource types, other platforms and MCP behavior were not tested. An equivalent workaround preserving this session result was not demonstrated. No severe harm or security-boundary crossing was observed. ### REA version Exact `node scripts/rea.mjs --version` stdout: ```text 6.8.0 ``` Source baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js v24.22.1; macOS 27.0.1; Darwin arm64. The protocol provider was the repository's synthetic FakeChrome/1.0 fixture, not an installed browser engine. ### Target and artifact context An HTTP/WebSocket CDP fixture bound to 127.0.0.1, exposing the `allowed-page` target. It reports a browser Timestamp of 20, a prior request at 20.5 and its redirect continuation at 21. Both hops use the approved loopback origin. The artifact inspected was the complete CLI JSON result and its normalized session timeline. ### Interface CLI ### Earliest failure stage Capture or observation ### Relevant output or evidence The redirect row from each invocation had the following values, with the ephemeral fixture port shown as PORT: ```json { "sequence": 1, "type": "redirect", "timestamp": 21, "frame_id": "frame-main", "loader_id": null, "request_id": "redirect-chain", "url": "http://127.0.0.1:PORT/next-hop", "destination_scope": "approved", "detail": null } ``` All four output documents lacked `/prior-hop` and `302`; `completeness.status` was `attach_limited`, `excluded` and `unavailable_sections` were empty, and `dropped_events.timeline_events` was 0. Expected source at the baseline: `docs/browser-observation.md:401-403` says, “Network redirect events retain their paired prior-hop response context when the timestamped next-request event falls inside the window.” This is the session timeline paragraph. The separate passive-inspection contract at `docs/browser-observation.md:454` describes prior-hop URLs and response status among retained redirect metadata; it does not define the exact session row schema. I also read `docs/web-runtime.md`: it documents execution and listener inspection and contains no redirect-row promise. Failure source: `src/browser/CdpNetworkEventFacts.ts:31-48` parses response URL and status, and `src/browser/CdpObservationSession.ts:501-530` admits this redirect but emits the next request URL with null detail. `src/domain/browserSession.ts:24-55` defines the timeline row without a response-context field. The supplied built producer has the corresponding behavior. The existing redirect assertion at `tests/boundary/browser/cdpObservationWindowBuffer.test.ts:196-203` checks type, request ID, timestamp and destination URL only. Scope controls at `tests/boundary/browser/cdpObservationWindowScope.test.ts:254-260` and `:435-439` do not require prior response context. These tests were inspected. The attempted `npm run test:local -- tests/boundary/browser/cdpObservationWindowBuffer.test.ts tests/boundary/browser/cdpObservationWindowScope.test.ts` exited before executing tests, directing the caller to use `test:focused` for built projects. No test-suite pass is claimed. ### Analysis or suspected cause Source-derived: the session redirect handler checks that the shared parsed redirect has a response, then constructs a destination-only timeline event. The response facts remain unused by that projection, and the public row schema has no dedicated response-context field. This agrees with the missing values in the executed CLI results; no implementation change was attempted. ### Additional context Read-only duplicate searches covered open and closed issues, merged and unmerged PRs, and issue comments, using redirect/session/timeline/context/prior-hop terms. No matching report was found. Related reports concern separate contracts: #7410 loses Location for browser-scenario request steps; #3172 handles nullable redirect fields in offline comparisons; #2593 handles final URL after scope exit; #5609 handles historical lifecycle events; #4392 handles timer overflow. The closed lifecycle issue does not claim to fix the response-context omission reproduced here. Merged PR #986 retains hops in passive page inspection, and closed PR #2045 proposes opt-in session HAR recording; neither supplies the documented session timeline context in this baseline.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4603
No description provided.