[GH-ISSUE #6755] [Bug] Markdown tables do not escape pipes and silently truncate rendered string values #4114

Closed
opened 2026-10-31 00:46:55 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

CLI

Observed behavior

--format md writes a literal pipe in an ordinary string directly into a table cell. In a one-row retained native inventory, the value left|right becomes the row | value | left|right |. GitHub's GFM renderer displays only left in the Value column and discards right. Both JSON and Markdown invocations exit 0; JSON retains the complete string.

Observed through the source CLI's createCli(...).serve(...) and createCliResultOutput boundaries, followed by one GitHub Markdown API rendering of the generated output. The packaged dispatcher and real providers were not exercised.

Reproduction

From a checkout with locked dependencies installed, save this as probe.mts and run node --import tsx ./probe.mts:

import { Writable } from 'node:stream';
import { writeFile } from 'node:fs/promises';
import { createCli } from './src/cli.ts';
import { createCliResultOutput } from './src/cli/cliResultOutput.ts';
import { createEvidence } from './src/domain/evidence.ts';
const parent = createEvidence(
  { path: '/tmp/fixture.exe', format: 'pe', sha256: 'a'.repeat(64) },
  { id: 'fixture', name: 'Fixture', version: '1' },
  { operation: 'list_names', parameters: {},
    result: [{ address: '0x0', value: 'left|right' }] },
);
const input = '/tmp/markdown-pipe-input.json';
await writeFile(input, JSON.stringify({ source: { kind: 'inline', evidence: parent },
  view: { kind: 'native-inventory', fields: ['value'] } }));
for (const format of ['json', 'md']) {
  const argv = ['inspect-analysis-view', input, '--format', format,
    '--filter-output', 'normalized_result.rows.0.record'];
  const chunks: Buffer[] = [];
  const destination = new Writable({ write(c, _e, cb) {
    chunks.push(Buffer.from(c)); cb();
  } });
  const output = createCliResultOutput(argv, destination)!;
  let fallback = '', exitCode = 0;
  await createCli(process.env, output).serve(argv, {
    stdout: s => { if (!output.handled) fallback += s; },
    exit: code => { exitCode = code; },
  });
  console.log(JSON.stringify({ format, exitCode,
    text: Buffer.concat(chunks).toString() + fallback }));
}

The executed probe used the same calls and fixture, saved each output to disk, and submitted the Markdown output to gh api markdown -f mode=gfm -F text=@output.md. One paired CLI probe and one rendering were run before drafting. No target file is opened.

Expected behavior

Markdown output should preserve a selected ordinary string in its table cell, escaping the pipe as necessary. The repository's Evidence contract requires preserving caller-selected inputs and local evidence, including output.

Impact and scope

A rendered Markdown report silently loses part of a selected string. The demonstrated case uses one short row and no nested cell values, so it does not require large output or resource pressure. The same JSON invocation preserves the value and is an observed workaround. Other Markdown consumers and other special characters were not tested.

REA version

Source 2bcd7e6d13f0a8118d6619718254255e84f75d86, package 6.7.0, locked Incur 0.6.1.

Runtime and platform

Node.js v24.22.1; Linux x64; locked dependencies installed with npm 11.16.0.

Target and artifact context

Synthetic retained list_names Evidence with one ordinary address/value record. No external target or live service under test.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

JSON output includes "value":"left|right". Markdown output, exit 0:

## normalized_result.rows.0.record

| Key     | Value      |
|---------|------------|
| address | 0x0        |
| value   | left|right |

The GitHub Markdown API's rendered row is:

<tr>
<td>value</td>
<td>left</td>
</tr>

Analysis or suspected cause

Source-derived: the CLI output owner delegates the filtered Markdown document to Incur's formatter. The locked node_modules/incur/src/Formatter.ts passes raw cell strings through pad() and joins them with |, without escaping literal pipes.

Additional context

Related #5525 concerns nested values becoming [object Object]; this report uses a plain scalar string. #6752 concerns call-argument exhaustion on large row pages; this report succeeds with one row but corrupts its rendered value.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The source CLI successfully emitted Markdown whose rendered table discarded part of a selected ordinary string; the equivalent JSON output retained it.
  • Workaround status: available
  • Workaround basis: The same paired probe returned the complete value with --format json.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6755 ## 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 CLI ## Observed behavior `--format md` writes a literal pipe in an ordinary string directly into a table cell. In a one-row retained native inventory, the value `left|right` becomes the row `| value | left|right |`. GitHub's GFM renderer displays only `left` in the Value column and discards `right`. Both JSON and Markdown invocations exit 0; JSON retains the complete string. Observed through the source CLI's `createCli(...).serve(...)` and `createCliResultOutput` boundaries, followed by one GitHub Markdown API rendering of the generated output. The packaged dispatcher and real providers were not exercised. ## Reproduction From a checkout with locked dependencies installed, save this as `probe.mts` and run `node --import tsx ./probe.mts`: ```ts import { Writable } from 'node:stream'; import { writeFile } from 'node:fs/promises'; import { createCli } from './src/cli.ts'; import { createCliResultOutput } from './src/cli/cliResultOutput.ts'; import { createEvidence } from './src/domain/evidence.ts'; const parent = createEvidence( { path: '/tmp/fixture.exe', format: 'pe', sha256: 'a'.repeat(64) }, { id: 'fixture', name: 'Fixture', version: '1' }, { operation: 'list_names', parameters: {}, result: [{ address: '0x0', value: 'left|right' }] }, ); const input = '/tmp/markdown-pipe-input.json'; await writeFile(input, JSON.stringify({ source: { kind: 'inline', evidence: parent }, view: { kind: 'native-inventory', fields: ['value'] } })); for (const format of ['json', 'md']) { const argv = ['inspect-analysis-view', input, '--format', format, '--filter-output', 'normalized_result.rows.0.record']; const chunks: Buffer[] = []; const destination = new Writable({ write(c, _e, cb) { chunks.push(Buffer.from(c)); cb(); } }); const output = createCliResultOutput(argv, destination)!; let fallback = '', exitCode = 0; await createCli(process.env, output).serve(argv, { stdout: s => { if (!output.handled) fallback += s; }, exit: code => { exitCode = code; }, }); console.log(JSON.stringify({ format, exitCode, text: Buffer.concat(chunks).toString() + fallback })); } ``` The executed probe used the same calls and fixture, saved each output to disk, and submitted the Markdown output to `gh api markdown -f mode=gfm -F text=@output.md`. One paired CLI probe and one rendering were run before drafting. No target file is opened. ## Expected behavior Markdown output should preserve a selected ordinary string in its table cell, escaping the pipe as necessary. The [repository's Evidence contract](https://github.com/morluto/rea/blob/2bcd7e6d13f0a8118d6619718254255e84f75d86/AGENTS.md#product-and-authority) requires preserving caller-selected inputs and local evidence, including output. ## Impact and scope A rendered Markdown report silently loses part of a selected string. The demonstrated case uses one short row and no nested cell values, so it does not require large output or resource pressure. The same JSON invocation preserves the value and is an observed workaround. Other Markdown consumers and other special characters were not tested. ## REA version Source `2bcd7e6d13f0a8118d6619718254255e84f75d86`, package `6.7.0`, locked Incur `0.6.1`. ## Runtime and platform Node.js `v24.22.1`; Linux x64; locked dependencies installed with npm `11.16.0`. ## Target and artifact context Synthetic retained `list_names` Evidence with one ordinary address/value record. No external target or live service under test. ## Interface CLI ## Earliest failure stage Transport or result projection ## Relevant output or evidence JSON output includes `"value":"left|right"`. Markdown output, exit 0: ```markdown ## normalized_result.rows.0.record | Key | Value | |---------|------------| | address | 0x0 | | value | left|right | ``` The GitHub Markdown API's rendered row is: ```html <tr> <td>value</td> <td>left</td> </tr> ``` ## Analysis or suspected cause Source-derived: the [CLI output owner](https://github.com/morluto/rea/blob/2bcd7e6d13f0a8118d6619718254255e84f75d86/src/cli/cliResultOutput.ts) delegates the filtered Markdown document to Incur's formatter. The locked `node_modules/incur/src/Formatter.ts` passes raw cell strings through `pad()` and joins them with ` | `, without escaping literal pipes. ## Additional context Related #5525 concerns nested values becoming `[object Object]`; this report uses a plain scalar string. #6752 concerns call-argument exhaustion on large row pages; this report succeeds with one row but corrupts its rendered value. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The source CLI successfully emitted Markdown whose rendered table discarded part of a selected ordinary string; the equivalent JSON output retained it. - Workaround status: available - Workaround basis: The same paired probe returned the complete value with `--format json`.
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#4114
No description provided.