[GH-ISSUE #3377] [Bug] Selected console text drops null primitive arguments #1243

Closed
opened 2026-10-16 21:03:15 +00:00 by bsakaguchi · 0 comments
Owner

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

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

inspect-web-page --include-console-text omits JavaScript null arguments from captured console text. A local page logging console.log(null, undefined, false) returns the latter two values but no value for argument zero. Its argument type is reduced to object, so the returned evidence cannot distinguish the null sentinel from an unexpanded object.

The inspection exits with code 0 and reports selected text as included, but retains only argument indices one and two. No argument-zero value or null subtype is retained. Both the full local capture and the reduced command below reproduced this result against the real browser. A separate loopback CDP fixture delivering the ordinary type: "object", subtype: "null", value: null console argument reproduced the same omission while adjacent primitive values survived.

Reproduction

  1. Start from a clean checkout at 8dde6370d0809ad04cda3342d4e6f290f48aa063 with Google Chrome installed in its standard macOS application directory. Install the locked dependencies and build the compiled runtime:
npm ci
npm run build:cached

The observed reproduction used dependencies and the compiled runtime that were already present; these installation and build commands are setup instructions.

  1. From the checkout root, run this self-contained command. It serves one local page with an inline-created /app.js script logging the three values every 100 ms, launches a separate headless browser profile with an ephemeral debugging port, waits until the page has loaded, and invokes the compiled CLI with console text selected. It prints one captured fixture event, stops its own browser and server, and deletes its temporary profile and isolated REA home:
node --input-type=module <<'JS'
import {createServer} from 'node:http';
import {spawn} from 'node:child_process';
import {mkdtemp, mkdir, readFile, rm} from 'node:fs/promises';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {once} from 'node:events';
const root=await mkdtemp(join(tmpdir(),'rea-console-'));
await mkdir(join(root,'home'));
const profile=join(root,'profile');
const server=createServer((req,res)=>{
 if(req.url==='/app.js'){
  res.setHeader('Content-Type','text/javascript');
  res.end('setInterval(() => console.log(null, undefined, false), 100);');
 }else{
  res.setHeader('Content-Type','text/html');
  res.end('<!doctype html><title>Console fixture</title><script src="/app.js"></script>');
 }
});
await new Promise(r=>server.listen(0,'127.0.0.1',r));
const pageUrl=`http://127.0.0.1:${server.address().port}/`;
const browser=spawn('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',[
 '--headless=new','--remote-debugging-port=0',`--user-data-dir=${profile}`,
 '--use-mock-keychain','--password-store=basic','--no-first-run','--no-default-browser-check',
 '--disable-background-networking','--disable-component-update','--disable-default-apps',
 '--disable-sync','--metrics-recording-only','--disable-extensions',
 '--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE 127.0.0.1',pageUrl
],{env:{...process.env,HOME:join(root,'home')},stdio:'ignore'});
const exited=once(browser,'exit');
try{
 let port;
 for(let i=0;i<200;i++){
  try{port=Number((await readFile(join(profile,'DevToolsActivePort'),'utf8')).split('\n')[0]);if(port)break;}catch{}
  if(browser.exitCode!==null)throw new Error('Browser exited during startup');
  await new Promise(r=>setTimeout(r,25));
 }
 if(!port)throw new Error('Browser did not publish its debugging port');
 const endpoint=`http://127.0.0.1:${port}`;let target;
 for(let i=0;i<200;i++){
  target=(await (await fetch(endpoint+'/json/list')).json()).find(t=>t.url===pageUrl&&t.title==='Console fixture');
  if(target)break;await new Promise(r=>setTimeout(r,25));
 }
 if(!target)throw new Error('Fixture did not finish loading');
 const result=await new Promise((resolveResult,reject)=>{
  const child=spawn(process.execPath,['scripts/rea.mjs','inspect-web-page',endpoint,target.id,'--observation-ms','500','--include-console-text','--json'],{env:{...process.env,HOME:join(root,'home')}});
  let output='';child.stdout.on('data',c=>output+=c);child.on('error',reject);child.on('close',code=>resolveResult({code,output}));
 });
 if(result.code!==0)throw new Error(result.output);
 const capture=JSON.parse(result.output).normalized_result;
 const event=capture.console.events.find(e=>e.url?.endsWith('/app.js'));
 if(!event)throw new Error('No fixture console event was observed');
 console.log(JSON.stringify({exit_code:result.code,browser:capture.browser.product,event}));
}finally{
 browser.kill('SIGTERM');const timer=setTimeout(()=>browser.kill('SIGKILL'),5000);
 await exited;clearTimeout(timer);await new Promise(r=>server.close(r));await rm(root,{recursive:true,force:true});
}
JS
  1. Inspect the printed event. The observed text_capture.values contains argument indices one and two, but no entry for argument index zero.

Expected behavior

Selected console text should retain the already-delivered null primitive at argument index zero, alongside undefined and false. Recognizing that primitive does not require reading a remote object, invoking a getter, or expanding any object. Unselected console text and genuinely unexpanded objects should retain their existing behavior.

docs/browser-observation.md:284 states that selected console text retains already-delivered primitive values verbatim while objects, getters, and remote properties are not expanded. CLI inspect-web-page --help advertises console message text selection.

Impact and scope

P2, supported by the observed reproduction. A normal primitive value is lost from explicitly requested browser evidence, and its retained top-level object type obscures whether the logged value was null. This affects null-bearing console calls while adjacent primitives remain usable.

No workaround is available. REA exposes no selection that restores the omitted argument. Changing the application's log statement to a string requires application control and changes the original runtime evidence.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64; Google Chrome 155.0.8059.40.

Target and artifact context

A local HTML page loads /app.js, which repeatedly logs null, undefined, and false. Chrome supplied real console events over its loopback CDP endpoint. The profile, local page, and browser process were created only for the reproduction and were removed afterward. A separate loopback CDP fixture confirmed handling of an ordinary null-subtype console argument. The evidence provenance is observed, local verification reproduced the failure, and the reproduction is complete.

Interface

CLI

Earliest failure stage

Capture or observation

Relevant output or evidence

The inspection exited with code 0. The captured fixture event has these relevant fields:

{
  "argument_types": ["object", "undefined", "boolean"],
  "text_capture": {
    "status": "included",
    "values": [
      {"argument_index": 1, "type": "undefined", "text": "undefined"},
      {"argument_index": 2, "type": "boolean", "text": "false"}
    ],
    "retained_bytes": 14
  }
}

Typed outcome or recovery expected

Capture the same page with console text selected. The returned event must represent the null primitive at argument index zero as well as the other two primitive values. Preserve original argument indices, correct retained-byte accounting, and the restriction against expanding remote objects. A control with console text unselected must still omit all selected text values. Genuinely unexpanded objects must retain their existing behavior.

Analysis or suspected cause

Source-derived reasoning: src/browser/CdpCaptureEventHelpers.ts:85 selects console primitive text by the producer's top-level type and has no null-subtype case. CDP represents the null primitive with top-level type: "object", subtype: "null", and value: null. src/browser/CdpCaptureEventHandlers.ts:482 skips that argument when constructing the selected text projection because the helper returns no primitive result. This source path is consistent with the observed real-browser and loopback-CDP omissions.

Additional context

Related open issue #3173 concerns missing network-content recovery guidance when console events differ during scenario comparison; this report concerns loss of a primitive argument during selected console capture.

Related open issue #3172 concerns false comparison certainty for unavailable redirect-response fields; this report concerns retaining an already-delivered console primitive.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3377 ### 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 `inspect-web-page --include-console-text` omits JavaScript `null` arguments from captured console text. A local page logging `console.log(null, undefined, false)` returns the latter two values but no value for argument zero. Its argument type is reduced to `object`, so the returned evidence cannot distinguish the null sentinel from an unexpanded object. The inspection exits with code 0 and reports selected text as `included`, but retains only argument indices one and two. No argument-zero value or null subtype is retained. Both the full local capture and the reduced command below reproduced this result against the real browser. A separate loopback CDP fixture delivering the ordinary `type: "object", subtype: "null", value: null` console argument reproduced the same omission while adjacent primitive values survived. ### Reproduction 1. Start from a clean checkout at `8dde6370d0809ad04cda3342d4e6f290f48aa063` with Google Chrome installed in its standard macOS application directory. Install the locked dependencies and build the compiled runtime: ```sh npm ci npm run build:cached ``` The observed reproduction used dependencies and the compiled runtime that were already present; these installation and build commands are setup instructions. 2. From the checkout root, run this self-contained command. It serves one local page with an inline-created `/app.js` script logging the three values every 100 ms, launches a separate headless browser profile with an ephemeral debugging port, waits until the page has loaded, and invokes the compiled CLI with console text selected. It prints one captured fixture event, stops its own browser and server, and deletes its temporary profile and isolated REA home: ```sh node --input-type=module <<'JS' import {createServer} from 'node:http'; import {spawn} from 'node:child_process'; import {mkdtemp, mkdir, readFile, rm} from 'node:fs/promises'; import {tmpdir} from 'node:os'; import {join} from 'node:path'; import {once} from 'node:events'; const root=await mkdtemp(join(tmpdir(),'rea-console-')); await mkdir(join(root,'home')); const profile=join(root,'profile'); const server=createServer((req,res)=>{ if(req.url==='/app.js'){ res.setHeader('Content-Type','text/javascript'); res.end('setInterval(() => console.log(null, undefined, false), 100);'); }else{ res.setHeader('Content-Type','text/html'); res.end('<!doctype html><title>Console fixture</title><script src="/app.js"></script>'); } }); await new Promise(r=>server.listen(0,'127.0.0.1',r)); const pageUrl=`http://127.0.0.1:${server.address().port}/`; const browser=spawn('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',[ '--headless=new','--remote-debugging-port=0',`--user-data-dir=${profile}`, '--use-mock-keychain','--password-store=basic','--no-first-run','--no-default-browser-check', '--disable-background-networking','--disable-component-update','--disable-default-apps', '--disable-sync','--metrics-recording-only','--disable-extensions', '--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE 127.0.0.1',pageUrl ],{env:{...process.env,HOME:join(root,'home')},stdio:'ignore'}); const exited=once(browser,'exit'); try{ let port; for(let i=0;i<200;i++){ try{port=Number((await readFile(join(profile,'DevToolsActivePort'),'utf8')).split('\n')[0]);if(port)break;}catch{} if(browser.exitCode!==null)throw new Error('Browser exited during startup'); await new Promise(r=>setTimeout(r,25)); } if(!port)throw new Error('Browser did not publish its debugging port'); const endpoint=`http://127.0.0.1:${port}`;let target; for(let i=0;i<200;i++){ target=(await (await fetch(endpoint+'/json/list')).json()).find(t=>t.url===pageUrl&&t.title==='Console fixture'); if(target)break;await new Promise(r=>setTimeout(r,25)); } if(!target)throw new Error('Fixture did not finish loading'); const result=await new Promise((resolveResult,reject)=>{ const child=spawn(process.execPath,['scripts/rea.mjs','inspect-web-page',endpoint,target.id,'--observation-ms','500','--include-console-text','--json'],{env:{...process.env,HOME:join(root,'home')}}); let output='';child.stdout.on('data',c=>output+=c);child.on('error',reject);child.on('close',code=>resolveResult({code,output})); }); if(result.code!==0)throw new Error(result.output); const capture=JSON.parse(result.output).normalized_result; const event=capture.console.events.find(e=>e.url?.endsWith('/app.js')); if(!event)throw new Error('No fixture console event was observed'); console.log(JSON.stringify({exit_code:result.code,browser:capture.browser.product,event})); }finally{ browser.kill('SIGTERM');const timer=setTimeout(()=>browser.kill('SIGKILL'),5000); await exited;clearTimeout(timer);await new Promise(r=>server.close(r));await rm(root,{recursive:true,force:true}); } JS ``` 3. Inspect the printed event. The observed `text_capture.values` contains argument indices one and two, but no entry for argument index zero. ### Expected behavior Selected console text should retain the already-delivered null primitive at argument index zero, alongside `undefined` and `false`. Recognizing that primitive does not require reading a remote object, invoking a getter, or expanding any object. Unselected console text and genuinely unexpanded objects should retain their existing behavior. `docs/browser-observation.md:284` states that selected console text retains already-delivered primitive values verbatim while objects, getters, and remote properties are not expanded. CLI `inspect-web-page --help` advertises console message text selection. ### Impact and scope P2, supported by the observed reproduction. A normal primitive value is lost from explicitly requested browser evidence, and its retained top-level object type obscures whether the logged value was null. This affects null-bearing console calls while adjacent primitives remain usable. No workaround is available. REA exposes no selection that restores the omitted argument. Changing the application's log statement to a string requires application control and changes the original runtime evidence. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64; Google Chrome 155.0.8059.40. ### Target and artifact context A local HTML page loads `/app.js`, which repeatedly logs `null`, `undefined`, and `false`. Chrome supplied real console events over its loopback CDP endpoint. The profile, local page, and browser process were created only for the reproduction and were removed afterward. A separate loopback CDP fixture confirmed handling of an ordinary null-subtype console argument. The evidence provenance is observed, local verification reproduced the failure, and the reproduction is complete. ### Interface CLI ### Earliest failure stage Capture or observation ### Relevant output or evidence The inspection exited with code 0. The captured fixture event has these relevant fields: ```text { "argument_types": ["object", "undefined", "boolean"], "text_capture": { "status": "included", "values": [ {"argument_index": 1, "type": "undefined", "text": "undefined"}, {"argument_index": 2, "type": "boolean", "text": "false"} ], "retained_bytes": 14 } } ``` ### Typed outcome or recovery expected Capture the same page with console text selected. The returned event must represent the null primitive at argument index zero as well as the other two primitive values. Preserve original argument indices, correct retained-byte accounting, and the restriction against expanding remote objects. A control with console text unselected must still omit all selected text values. Genuinely unexpanded objects must retain their existing behavior. ### Analysis or suspected cause Source-derived reasoning: `src/browser/CdpCaptureEventHelpers.ts:85` selects console primitive text by the producer's top-level type and has no null-subtype case. CDP represents the null primitive with top-level `type: "object"`, `subtype: "null"`, and `value: null`. `src/browser/CdpCaptureEventHandlers.ts:482` skips that argument when constructing the selected text projection because the helper returns no primitive result. This source path is consistent with the observed real-browser and loopback-CDP omissions. ### Additional context Related open issue [#3173](https://github.com/morluto/rea/issues/3173) concerns missing network-content recovery guidance when console events differ during scenario comparison; this report concerns loss of a primitive argument during selected console capture. Related open issue [#3172](https://github.com/morluto/rea/issues/3172) concerns false comparison certainty for unavailable redirect-response fields; this report concerns retaining an already-delivered console primitive.
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#1243
No description provided.