[GH-ISSUE #7687] [Bug] Target listing claims all HTTP(S) pages are listed while reporting a transport exclusion #4609

Open
opened 2026-10-31 00:51:18 +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/7687

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

Against a loopback HTTP discovery fixture with two HTTP page rows, node scripts/rea.mjs list-browser-targets ENDPOINT --json exits successfully and lists the one page with an admitted socket. Its normalized_result.limitations simultaneously says:

  • All page targets with supported HTTP(S) URLs are listed.
  • 1 otherwise allowed page target(s) lacked a validated direct CDP WebSocket and were excluded.

The omission warning correctly describes the missing page. The defect is the adjacent claim that every page with a supported HTTP(S) URL was listed. The excluded page has a supported HTTP URL; its missing transport is a separate property.

Reproduction

  1. Use the already-built baseline below with Node.js v24.22.1. From the checkout root, run this inline fixture. It binds only to 127.0.0.1, isolates the CLI's home/configuration, places package-manager stubs first on PATH, and closes and deletes its fixture state afterward. No browser or WebSocket connection is needed.
node --input-type=module <<'JS'
import { createServer } from 'node:http';
import { spawn } from 'node:child_process';
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';

const trial = await mkdtemp(join(tmpdir(), 'rea-discovery-'));
const env = {
  PATH: join(trial, 'bin') + ':' + dirname(process.execPath) + ':/usr/bin:/bin',
  npm_config_prefix: join(trial, 'npm-global'),
};
for (const key of ['HOME', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME',
                   'XDG_DATA_HOME', 'XDG_STATE_HOME']) {
  env[key] = join(trial, key);
  await mkdir(env[key], { recursive: true });
}
await mkdir(join(trial, 'bin'));
for (const name of ['npx', 'npm', 'bun']) {
  await writeFile(join(trial, 'bin', name),
    '#!/bin/sh\nif [ "$1" = "--version" ]; then echo 0.0.0-fixture; exit 0; fi\nexit 97\n',
    { mode: 0o755 });
}
let complete = false;
const requests = [];
const server = createServer((request, response) => {
  requests.push(request.url);
  const port = server.address().port;
  response.setHeader('content-type', 'application/json');
  if (request.url === '/json/version') {
    response.end(JSON.stringify({
      Browser: 'Chrome/154.0.0.0', 'Protocol-Version': '1.3',
      'User-Agent': 'fixture', 'V8-Version': '15.4',
      'WebKit-Version': '537.36 (@fixture)',
      webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/page/AAA`,
    }));
  } else if (request.url === '/json/list') {
    response.end(JSON.stringify([
      { id: 'AAA', type: 'page', title: 'Alpha', attached: false,
        url: 'http://127.0.0.1:3000/a',
        webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/page/AAA` },
      { id: 'BBB', type: 'page', title: 'Beta', attached: false,
        url: 'http://127.0.0.1:3000/b',
        ...(complete ? {
          webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/page/BBB`,
        } : {}) },
    ]));
  } else response.writeHead(404).end();
});
try {
  await new Promise((resolve, reject) => {
    server.once('error', reject);
    server.listen(0, '127.0.0.1', resolve);
  });
  const endpoint = `http://127.0.0.1:${server.address().port}`;
  for (const mode of ['partial-1', 'partial-2', 'complete-control']) {
    complete = mode === 'complete-control';
    const result = await new Promise((resolve, reject) => {
      const child = spawn(process.execPath,
        ['scripts/rea.mjs', 'list-browser-targets', endpoint, '--json'],
        { env });
      let stdout = '', stderr = '';
      child.stdout.on('data', data => stdout += data);
      child.stderr.on('data', data => stderr += data);
      child.once('error', reject);
      child.once('close', code => resolve({ code, stdout, stderr }));
    });
    if (result.code !== 0) throw new Error(JSON.stringify(result));
    const listed = JSON.parse(result.stdout).normalized_result;
    console.log(JSON.stringify({ mode, exit: result.code,
      targets: listed.targets.map(target => target.target_id),
      excluded: listed.excluded, limitations: listed.limitations }));
  }
  console.log('HTTP requests:', JSON.stringify(requests));
} finally {
  if (server.listening) await new Promise((resolve, reject) =>
    server.close(error => error ? reject(error) : resolve()));
  await rm(trial, { recursive: true, force: true });
}
JS
  1. Compare the two partial results with complete-control. Both partial runs return only AAA and both contested sentences. Adding only BBB's direct target socket returns AAA and BBB and removes the exclusion warning.

The initial independent probe reproduced the partial shape twice and the complete control once. The inline procedure above was also executed successfully with the same three results.

Expected behavior

The completeness wording should describe the targets actually eligible for listing, including the transport prerequisite, or omit the all-pages assertion when pages were excluded for missing transport. It must not claim that every page with a supported HTTP(S) URL was listed while acknowledging an otherwise allowed HTTP page was excluded.

The documented contract distinguishes socket validation from target identity, describes listing all eligible pages, and promises one complete in-scope array. This does not require listing pages without usable transports; the existing omission warning should remain truthful.

Impact and scope

P3: this verification establishes contradictory result wording, with an accurate omission warning available immediately beside it. No failed downstream agent decision, lost browser state, or inability to select an otherwise usable target was observed. Callers can read the exclusion warning to recognize the limited inventory.

The exercised trigger is HTTP discovery whose version socket addresses only AAA, with a second HTTP page BBB lacking its own validated socket, and no origin filter. Frequency in real browsers is unknown. The control shows that a second valid per-target socket removes the contradiction. The three named structured exclusion counters remain zero in both shapes; they are not documented as a total omission count, so their values are context rather than a separate defect claim.

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; arm64. Existing compiled runtime, invoked through node scripts/rea.mjs. The fixture serves HTTP discovery documents only.

Target and artifact context

Two synthetic page rows on one HTTP origin. The version document provides a page-scoped socket for AAA; AAA also supplies an explicit matching direct socket. BBB has no socket until the control run. No real browser, page content, target session, or artifact is involved.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Both partial runs exited 0 with this relevant projection:

{
  "targets": ["AAA"],
  "excluded": {"disallowed_origin": 0, "unsupported_url": 0, "non_page": 0},
  "limitations": [
    "All page targets with supported HTTP(S) URLs are listed.",
    "1 otherwise allowed page target(s) lacked a validated direct CDP WebSocket and were excluded."
  ]
}

targets above is projected to IDs; the actual array contains the target objects. The control returned ["AAA", "BBB"] and only the first limitation. The HTTP request log contained one /json/version and one /json/list per invocation.

Baseline source and contract locators:

  • docs/browser-observation.md:15-20: validates target identity and socket authority independently.
  • docs/browser-observation.md:24 and :121: eligible-page listing and the complete in-scope array.
  • src/browser/CdpBrowserProvider.ts:157-166: emits the first sentence based on the absence of an origin filter, independently of the transport exclusion count.
  • src/browser/CdpBrowserProvider.ts:533-583 and src/browser/CdpEndpoint.ts:342-358: separate URL/origin admission from usable transport and count the excluded pages.
  • tests/boundary/browser/cdpBrowserProvider.discovery_authorization.test.ts:133-183: expects this transport-based exclusion, establishing that selection itself is intentional.

Typed outcome or recovery expected

Preserve transport admission and the missing-socket warning. Correct the completeness wording to acknowledge the subset that has validated transports.

Additional context

Read-only duplicate searches included open and closed issues, issue comments, and open, closed, and merged PRs. No report of this contradictory sentence was found. Issue #194 and merged PR #218 introduced page-scoped transport support and intentional missing-transport exclusions; they address endpoint compatibility, not this completeness wording. Issue #2537 concerns Inspector discovery losing all usable siblings, and #5947 concerns missing-target remediation. Neither shares this failure or its restoration check.

Triage assessment

  • Impact level: P3
  • Assessment status: supported
  • Impact basis: The public CLI emits contradictory inventory wording, but accurately warns about the omitted page in the same result; no consequential caller failure was observed.
  • Workaround status: available
  • Workaround basis: Read the explicit missing-transport limitation rather than relying on the all-pages sentence; the returned result already supplies the correct exclusion count in that warning.
Originally created by @coygeek on GitHub (Oct 28, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7687 ### 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 Against a loopback HTTP discovery fixture with two HTTP page rows, `node scripts/rea.mjs list-browser-targets ENDPOINT --json` exits successfully and lists the one page with an admitted socket. Its `normalized_result.limitations` simultaneously says: - `All page targets with supported HTTP(S) URLs are listed.` - `1 otherwise allowed page target(s) lacked a validated direct CDP WebSocket and were excluded.` The omission warning correctly describes the missing page. The defect is the adjacent claim that every page with a supported HTTP(S) URL was listed. The excluded page has a supported HTTP URL; its missing transport is a separate property. ### Reproduction 1. Use the already-built baseline below with Node.js v24.22.1. From the checkout root, run this inline fixture. It binds only to `127.0.0.1`, isolates the CLI's home/configuration, places package-manager stubs first on PATH, and closes and deletes its fixture state afterward. No browser or WebSocket connection is needed. ```sh node --input-type=module <<'JS' import { createServer } from 'node:http'; import { spawn } from 'node:child_process'; import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, dirname } from 'node:path'; const trial = await mkdtemp(join(tmpdir(), 'rea-discovery-')); const env = { PATH: join(trial, 'bin') + ':' + dirname(process.execPath) + ':/usr/bin:/bin', npm_config_prefix: join(trial, 'npm-global'), }; for (const key of ['HOME', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME']) { env[key] = join(trial, key); await mkdir(env[key], { recursive: true }); } await mkdir(join(trial, 'bin')); for (const name of ['npx', 'npm', 'bun']) { await writeFile(join(trial, 'bin', name), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo 0.0.0-fixture; exit 0; fi\nexit 97\n', { mode: 0o755 }); } let complete = false; const requests = []; const server = createServer((request, response) => { requests.push(request.url); const port = server.address().port; response.setHeader('content-type', 'application/json'); if (request.url === '/json/version') { response.end(JSON.stringify({ Browser: 'Chrome/154.0.0.0', 'Protocol-Version': '1.3', 'User-Agent': 'fixture', 'V8-Version': '15.4', 'WebKit-Version': '537.36 (@fixture)', webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/page/AAA`, })); } else if (request.url === '/json/list') { response.end(JSON.stringify([ { id: 'AAA', type: 'page', title: 'Alpha', attached: false, url: 'http://127.0.0.1:3000/a', webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/page/AAA` }, { id: 'BBB', type: 'page', title: 'Beta', attached: false, url: 'http://127.0.0.1:3000/b', ...(complete ? { webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/page/BBB`, } : {}) }, ])); } else response.writeHead(404).end(); }); try { await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); const endpoint = `http://127.0.0.1:${server.address().port}`; for (const mode of ['partial-1', 'partial-2', 'complete-control']) { complete = mode === 'complete-control'; const result = await new Promise((resolve, reject) => { const child = spawn(process.execPath, ['scripts/rea.mjs', 'list-browser-targets', endpoint, '--json'], { env }); let stdout = '', stderr = ''; child.stdout.on('data', data => stdout += data); child.stderr.on('data', data => stderr += data); child.once('error', reject); child.once('close', code => resolve({ code, stdout, stderr })); }); if (result.code !== 0) throw new Error(JSON.stringify(result)); const listed = JSON.parse(result.stdout).normalized_result; console.log(JSON.stringify({ mode, exit: result.code, targets: listed.targets.map(target => target.target_id), excluded: listed.excluded, limitations: listed.limitations })); } console.log('HTTP requests:', JSON.stringify(requests)); } finally { if (server.listening) await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); await rm(trial, { recursive: true, force: true }); } JS ``` 2. Compare the two `partial` results with `complete-control`. Both partial runs return only `AAA` and both contested sentences. Adding only BBB's direct target socket returns `AAA` and `BBB` and removes the exclusion warning. The initial independent probe reproduced the partial shape twice and the complete control once. The inline procedure above was also executed successfully with the same three results. ### Expected behavior The completeness wording should describe the targets actually eligible for listing, including the transport prerequisite, or omit the all-pages assertion when pages were excluded for missing transport. It must not claim that every page with a supported HTTP(S) URL was listed while acknowledging an otherwise allowed HTTP page was excluded. The documented contract distinguishes socket validation from target identity, describes listing all eligible pages, and promises one complete in-scope array. This does not require listing pages without usable transports; the existing omission warning should remain truthful. ### Impact and scope P3: this verification establishes contradictory result wording, with an accurate omission warning available immediately beside it. No failed downstream agent decision, lost browser state, or inability to select an otherwise usable target was observed. Callers can read the exclusion warning to recognize the limited inventory. The exercised trigger is HTTP discovery whose version socket addresses only AAA, with a second HTTP page BBB lacking its own validated socket, and no origin filter. Frequency in real browsers is unknown. The control shows that a second valid per-target socket removes the contradiction. The three named structured exclusion counters remain zero in both shapes; they are not documented as a total omission count, so their values are context rather than a separate defect claim. ### 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; arm64. Existing compiled runtime, invoked through `node scripts/rea.mjs`. The fixture serves HTTP discovery documents only. ### Target and artifact context Two synthetic page rows on one HTTP origin. The version document provides a page-scoped socket for AAA; AAA also supplies an explicit matching direct socket. BBB has no socket until the control run. No real browser, page content, target session, or artifact is involved. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence Both partial runs exited 0 with this relevant projection: ```json { "targets": ["AAA"], "excluded": {"disallowed_origin": 0, "unsupported_url": 0, "non_page": 0}, "limitations": [ "All page targets with supported HTTP(S) URLs are listed.", "1 otherwise allowed page target(s) lacked a validated direct CDP WebSocket and were excluded." ] } ``` `targets` above is projected to IDs; the actual array contains the target objects. The control returned `["AAA", "BBB"]` and only the first limitation. The HTTP request log contained one `/json/version` and one `/json/list` per invocation. Baseline source and contract locators: - `docs/browser-observation.md:15-20`: validates target identity and socket authority independently. - `docs/browser-observation.md:24` and `:121`: eligible-page listing and the complete in-scope array. - `src/browser/CdpBrowserProvider.ts:157-166`: emits the first sentence based on the absence of an origin filter, independently of the transport exclusion count. - `src/browser/CdpBrowserProvider.ts:533-583` and `src/browser/CdpEndpoint.ts:342-358`: separate URL/origin admission from usable transport and count the excluded pages. - `tests/boundary/browser/cdpBrowserProvider.discovery_authorization.test.ts:133-183`: expects this transport-based exclusion, establishing that selection itself is intentional. ### Typed outcome or recovery expected Preserve transport admission and the missing-socket warning. Correct the completeness wording to acknowledge the subset that has validated transports. ### Additional context Read-only duplicate searches included open and closed issues, issue comments, and open, closed, and merged PRs. No report of this contradictory sentence was found. Issue #194 and merged PR #218 introduced page-scoped transport support and intentional missing-transport exclusions; they address endpoint compatibility, not this completeness wording. Issue #2537 concerns Inspector discovery losing all usable siblings, and #5947 concerns missing-target remediation. Neither shares this failure or its restoration check. ## Triage assessment - Impact level: P3 - Assessment status: supported - Impact basis: The public CLI emits contradictory inventory wording, but accurately warns about the omitted page in the same result; no consequential caller failure was observed. - Workaround status: available - Workaround basis: Read the explicit missing-transport limitation rather than relying on the all-pages sentence; the returned result already supplies the correct exclusion count in that warning.
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#4609
No description provided.