[GH-ISSUE #4003] [Bug] Process capture leaves endpoint ports numeric when port normalization is enabled #1593

Closed
opened 2026-10-16 21:07:36 +00:00 by bsakaguchi · 2 comments
Owner

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

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

capture-process leaves localhost:8080 numeric with normalization.ports: true when the PTY chunk contains only that endpoint. Adding an explicit port:8080 field to the same chunk makes both ports normalize. The public CLI probe below produced these results on the reported baseline; all captures succeeded, reported truncated: false, and verified cleanup.

Reproduction

Use the source baseline in REA version on a supported process-capture host with the native PTY backend available. From the repository root, run this public CLI probe. It creates an isolated home and temporary directory, captures three harmless Node commands, prints the relevant observations, and removes its fixtures.

node --input-type=module <<'JS'
import { spawnSync } from 'node:child_process';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const work = mkdtempSync(join(tmpdir(), 'process-port-probe-'));
mkdirSync(join(work, 'home'));
try {
  for (const text of ['localhost:8080', 'port:8080', 'localhost:8080 port:8080']) {
    const scenario = join(work, 'scenario.json');
    writeFileSync(scenario, JSON.stringify({
      executable: process.execPath,
      arguments: ['-e', `console.log(${JSON.stringify(text)})`],
      timeout_ms: 5000,
      normalization: { pids: false, ports: true }
    }));
    const run = spawnSync(process.execPath,
      ['scripts/rea.mjs', 'capture-process', scenario, '--json'],
      { encoding: 'utf8', timeout: 30000,
        env: { ...process.env, HOME: join(work, 'home'), TMPDIR: work } });
    if (run.error) throw run.error;
    const capture = JSON.parse(run.stdout).normalized_result;
    console.log(JSON.stringify({ input: text, status: run.status,
      frames: capture.frames.map(({ data, raw_data }) => ({ data, raw_data })),
      rendered: capture.rendered_frames[0].lines[0],
      truncated: capture.truncated }));
  }
} finally { rmSync(work, { recursive: true, force: true }); }
JS

Expected behavior

With port normalization enabled, endpoint-only normalized text should be localhost:<port>\r\n, its rendered first line should be localhost:<port>, and raw_data should retain the original endpoint. An unrelated port field must not be required. docs/process-capture.md:269-278 documents URL authorities, IP endpoints, and localhost endpoints as supported port forms. Lines 244-258 define normalized frame/rendered text and original-chunk retention. Ordinary counters and ports: false must retain their existing semantics.

Impact and scope

Normalized PTY frames and rendered comparison text can retain ephemeral endpoint differences despite the selected normalization. The reproduced scope is a native PTY capture through the public CLI. A separate CLI control using a literal pattern from localhost:8080 to localhost:<port> produced the intended endpoint-only output, providing a workaround for a known endpoint spelling. No external provider or network service was involved.

REA version

rea 6.5.0 at source commit 4692bb3330; source freshness checked at origin/main 6fcfc5b5e0

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Harmless Node commands printing endpoint-only, explicit-field-only, and mixed text; returned capture Evidence frames and rendered terminal lines.

Interface

CLI

Earliest failure stage

Capture or observation

Relevant output or evidence

The executed probe produced:

Input Normalized frame data raw_data First rendered line
localhost:8080 localhost:8080\r\n absent localhost:8080
port:8080 port:<port>\r\n original chunk port:<port>
localhost:8080 port:8080 localhost:<port> port:<port>\r\n original chunk localhost:<port> port:<port>

The existing focused src/process/capture/ProcessNormalization.test.ts suite passed all 17 tests during the original reproduction.

Source freshness checked against current origin/main 6fcfc5b5e0c063300cd5278dd8bad5b1ab1374f6. The cited production files and contract documentation are unchanged from the reproduced baseline; the source-derived failure still holds. The full runtime reproduction was not rerun for this freshness check.

Typed outcome or recovery expected

Normalize endpoint-only chunks and preserve the original text. Preserve the current field-only and mixed results, disabled normalization, and ordinary numeric counters.

Analysis or suspected cause

Source-derived: src/process/capture/ProcessNormalization.ts:365-384 computes the endpoint-stage result, then returns only the following field-stage result. If the field stage reports unchanged, its caller at lines 215-235 retains the original input and drops the endpoint change. src/process/capture/ProcessHarness.ts:278 selects this bounded normalizer for production capture.

Additional context

Related closed report #1241 concerns overly broad normalization of ordinary JSON numbers. This report concerns a documented endpoint that does not normalize, with a different trigger and restoration condition.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4003 ### 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 `capture-process` leaves `localhost:8080` numeric with `normalization.ports: true` when the PTY chunk contains only that endpoint. Adding an explicit `port:8080` field to the same chunk makes both ports normalize. The public CLI probe below produced these results on the reported baseline; all captures succeeded, reported `truncated: false`, and verified cleanup. ### Reproduction Use the source baseline in REA version on a supported process-capture host with the native PTY backend available. From the repository root, run this public CLI probe. It creates an isolated home and temporary directory, captures three harmless Node commands, prints the relevant observations, and removes its fixtures. ```sh node --input-type=module <<'JS' import { spawnSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; const work = mkdtempSync(join(tmpdir(), 'process-port-probe-')); mkdirSync(join(work, 'home')); try { for (const text of ['localhost:8080', 'port:8080', 'localhost:8080 port:8080']) { const scenario = join(work, 'scenario.json'); writeFileSync(scenario, JSON.stringify({ executable: process.execPath, arguments: ['-e', `console.log(${JSON.stringify(text)})`], timeout_ms: 5000, normalization: { pids: false, ports: true } })); const run = spawnSync(process.execPath, ['scripts/rea.mjs', 'capture-process', scenario, '--json'], { encoding: 'utf8', timeout: 30000, env: { ...process.env, HOME: join(work, 'home'), TMPDIR: work } }); if (run.error) throw run.error; const capture = JSON.parse(run.stdout).normalized_result; console.log(JSON.stringify({ input: text, status: run.status, frames: capture.frames.map(({ data, raw_data }) => ({ data, raw_data })), rendered: capture.rendered_frames[0].lines[0], truncated: capture.truncated })); } } finally { rmSync(work, { recursive: true, force: true }); } JS ``` ### Expected behavior With port normalization enabled, endpoint-only normalized text should be `localhost:<port>\r\n`, its rendered first line should be `localhost:<port>`, and `raw_data` should retain the original endpoint. An unrelated port field must not be required. `docs/process-capture.md:269-278` documents URL authorities, IP endpoints, and localhost endpoints as supported port forms. Lines 244-258 define normalized frame/rendered text and original-chunk retention. Ordinary counters and `ports: false` must retain their existing semantics. ### Impact and scope Normalized PTY frames and rendered comparison text can retain ephemeral endpoint differences despite the selected normalization. The reproduced scope is a native PTY capture through the public CLI. A separate CLI control using a literal pattern from `localhost:8080` to `localhost:<port>` produced the intended endpoint-only output, providing a workaround for a known endpoint spelling. No external provider or network service was involved. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860; source freshness checked at origin/main 6fcfc5b5e0c063300cd5278dd8bad5b1ab1374f6 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Harmless Node commands printing endpoint-only, explicit-field-only, and mixed text; returned capture Evidence frames and rendered terminal lines. ### Interface CLI ### Earliest failure stage Capture or observation ### Relevant output or evidence The executed probe produced: | Input | Normalized frame `data` | `raw_data` | First rendered line | | --- | --- | --- | --- | | `localhost:8080` | `localhost:8080\r\n` | absent | `localhost:8080` | | `port:8080` | `port:<port>\r\n` | original chunk | `port:<port>` | | `localhost:8080 port:8080` | `localhost:<port> port:<port>\r\n` | original chunk | `localhost:<port> port:<port>` | The existing focused `src/process/capture/ProcessNormalization.test.ts` suite passed all 17 tests during the original reproduction. Source freshness checked against current `origin/main` `6fcfc5b5e0c063300cd5278dd8bad5b1ab1374f6`. The cited production files and contract documentation are unchanged from the reproduced baseline; the source-derived failure still holds. The full runtime reproduction was not rerun for this freshness check. ### Typed outcome or recovery expected Normalize endpoint-only chunks and preserve the original text. Preserve the current field-only and mixed results, disabled normalization, and ordinary numeric counters. ### Analysis or suspected cause Source-derived: `src/process/capture/ProcessNormalization.ts:365-384` computes the endpoint-stage result, then returns only the following field-stage result. If the field stage reports `unchanged`, its caller at lines 215-235 retains the original input and drops the endpoint change. `src/process/capture/ProcessHarness.ts:278` selects this bounded normalizer for production capture. ### Additional context Related closed report #1241 concerns overly broad normalization of ordinary JSON numbers. This report concerns a documented endpoint that does not normalize, with a different trigger and restoration condition.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Confirmed — with normalization.ports: true, a PTY chunk containing only an endpoint like "localhost:8080" stays numeric, while the same chunk plus an explicit "port:8080" field normalizes both. That means port normalization only triggers on the explicit-port path, leaving endpoint-only output unnormalized. The normalization pass should also match bare "host:port" endpoint tokens so the declared setting applies consistently regardless of whether an explicit port field is present.

<!-- gh-comment-id:6187479981 --> @XinwangTao commented on GitHub (Oct 15, 2026): Confirmed — with normalization.ports: true, a PTY chunk containing only an endpoint like "localhost:8080" stays numeric, while the same chunk plus an explicit "port:8080" field normalizes both. That means port normalization only triggers on the explicit-port path, leaving endpoint-only output unnormalized. The normalization pass should also match bare "host:port" endpoint tokens so the declared setting applies consistently regardless of whether an explicit port field is present.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

The revised #3848 approach — memoizing expansion by (getter, receiver, remaining path, escape mode) with a 512-state budget as a safety net — addresses exactly the weakness the budget-only version had: a class with ~130 plain getters tripping the coarse budget and marking the whole file unknown. Scoping the degradation to the affected query instead of the whole file is the right call. Will take a look at the PR.

<!-- gh-comment-id:6188061200 --> @XinwangTao commented on GitHub (Oct 15, 2026): The revised #3848 approach — memoizing expansion by (getter, receiver, remaining path, escape mode) with a 512-state budget as a safety net — addresses exactly the weakness the budget-only version had: a class with ~130 plain getters tripping the coarse budget and marking the whole file unknown. Scoping the degradation to the affected query instead of the whole file is the right call. Will take a look at the PR.
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#1593
No description provided.