[GH-ISSUE #5525] [Bug] CLI --format md replaces nested inventory facts with [object Object] #3361

Closed
opened 2026-10-31 00:39:33 +00:00 by bsakaguchi · 4 comments
Owner

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

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

node scripts/rea.mjs inspect-artifact "$work/fixture.zip" --format md succeeds but omits both entry names from a valid two-entry ZIP. Nested Evidence and observation cells contain [object Object]. The same command with --format json preserves both names. Both commands exit 0 with empty stderr.

The independently verified root cause is Incur 0.6.1's Markdown formatter, which REA delegates to. A standalone Incur CLI reproduces the same coercion without REA code.

Reproduction

From the built source checkout below, run this independently reproduced public CLI comparison. It uses a locally generated ZIP and isolated home/XDG directories; no external provider or API is required.

work="$(mktemp -d)"
export HOME="$work/home"
export XDG_CONFIG_HOME="$work/config"
export XDG_DATA_HOME="$work/data"
export XDG_CACHE_HOME="$work/cache"
export XDG_STATE_HOME="$work/state"
mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME"
python3 - "$work/fixture.zip" <<'PYTHON'
import sys
import zipfile
with zipfile.ZipFile(sys.argv[1], "w") as archive:
    archive.writestr("alpha.txt", "hello\n")
    archive.writestr("sub/beta.txt", "world\n")
PYTHON
node scripts/rea.mjs inspect-artifact "$work/fixture.zip" --format md > "$work/zip.md"
node scripts/rea.mjs inspect-artifact "$work/fixture.zip" --format json > "$work/zip.json"
python3 - "$work/zip.md" "$work/zip.json" <<'PYTHON'
import pathlib
import sys
for filename in sys.argv[1:]:
    text = pathlib.Path(filename).read_text()
    print(pathlib.Path(filename).name,
          "alpha.txt=" + str("alpha.txt" in text),
          "sub/beta.txt=" + str("sub/beta.txt" in text),
          "object_markers=" + str(text.count("[object Object]")))
PYTHON

Expected behavior

Markdown should preserve the archive entry facts, including alpha.txt and sub/beta.txt, and their association with the inventory. No summary, filter, or token window was requested.

inspect-artifact --help advertises all available observations and complete Evidence by default, and advertises Markdown as an output format. docs/cli.md:3-5 states CLI/MCP workflow and Evidence parity. docs/tool-design.md:51-56 requires requested facts inline with access to complete Evidence.

Impact and scope

The advertised Markdown representation silently omits requested inventory facts despite successful exit status. Selecting --format json is the directly verified workaround. The verifier also observed 31 [object Object] occurrences on eight lines from capabilities --format md; its JSON control returned four structured capability records in that environment.

Observed scope is nested values in object-array table rows and these archive/capability cases. This does not establish that every nested object loses its contents. No source data loss or security boundary crossing was observed. Broader input and platform coverage was not tested.

REA version

6.7.0, source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8, with lockfile-pinned Incur 0.6.1.

Runtime and platform

Node.js v26.12.0; macOS, Darwin arm64. No Hopper or Ghidra provider was required.

Target and artifact context

A generated ZIP containing alpha.txt with hello\n and sub/beta.txt with world\n. No private target data is involved.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The executed archive comparison printed:

zip.md alpha.txt=False sub/beta.txt=False object_markers=2
zip.json alpha.txt=True sub/beta.txt=True object_markers=0

The JSON control retains both names under normalized_result.substeps[0].evidence.normalized_result.occurrences.

The following standalone Incur CLI probe was also executed from the checkout with its pinned dependency:

node --input-type=module --eval 'import { Cli } from "incur"; await Cli.create("probe").command("rows", { run() { return [{ details: { path: "alpha.txt" }, entries: [{ path: "sub/beta.txt" }] }]; } }).serve(["rows", "--format", "md"]);'

It exits 0 and prints:

| details         | entries         |
|-----------------|-----------------|
| [object Object] | [object Object] |

Typed outcome or recovery expected

A successful Markdown result should retain the nested Evidence facts. The JSON control should continue to retain both entry names. Suppressing the affected columns would still omit requested facts.

Analysis or suspected cause

Source-derived reasoning, corroborated by the standalone reproduction: src/cli/cliResultOutput.ts:241-245 delegates successful non-JSON rendering to Incur. The bundled node_modules/incur/dist/Cli.js:1121-1129 calls its formatter. node_modules/incur/src/Formatter.ts:73-79, compiled at dist/Formatter.js:63-65, applies String(...) to object-array table cells. The producing formatter belongs to upstream wevm/incur.

The choice of remedy is left to REA's maintainers, such as flattening these values in REA, pinning or patching Incur, or documenting the limitation.

Additional context

Prepared with AI assistance and independently reproduced through the public CLI before submission. The standalone Incur probe was also replayed during submission preparation.

A fresh fetch through c374c2fe7290b914d73d9e27af38b498753ec593 found unchanged dependency pins and no later fix for this successful Markdown path. The subsequent CLI change preserves typed failures under filters and still delegates successful non-JSON results to Incur. This upstream check is source-derived; the REA runtime reproduction targets the baseline above.

NOT_ENOUGH_INFO: the last known good version and first affected version have not been established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5525 ### 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 `node scripts/rea.mjs inspect-artifact "$work/fixture.zip" --format md` succeeds but omits both entry names from a valid two-entry ZIP. Nested Evidence and observation cells contain `[object Object]`. The same command with `--format json` preserves both names. Both commands exit 0 with empty stderr. The independently verified root cause is Incur 0.6.1's Markdown formatter, which REA delegates to. A standalone Incur CLI reproduces the same coercion without REA code. ### Reproduction From the built source checkout below, run this independently reproduced public CLI comparison. It uses a locally generated ZIP and isolated home/XDG directories; no external provider or API is required. ```sh work="$(mktemp -d)" export HOME="$work/home" export XDG_CONFIG_HOME="$work/config" export XDG_DATA_HOME="$work/data" export XDG_CACHE_HOME="$work/cache" export XDG_STATE_HOME="$work/state" mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME" "$XDG_STATE_HOME" python3 - "$work/fixture.zip" <<'PYTHON' import sys import zipfile with zipfile.ZipFile(sys.argv[1], "w") as archive: archive.writestr("alpha.txt", "hello\n") archive.writestr("sub/beta.txt", "world\n") PYTHON node scripts/rea.mjs inspect-artifact "$work/fixture.zip" --format md > "$work/zip.md" node scripts/rea.mjs inspect-artifact "$work/fixture.zip" --format json > "$work/zip.json" python3 - "$work/zip.md" "$work/zip.json" <<'PYTHON' import pathlib import sys for filename in sys.argv[1:]: text = pathlib.Path(filename).read_text() print(pathlib.Path(filename).name, "alpha.txt=" + str("alpha.txt" in text), "sub/beta.txt=" + str("sub/beta.txt" in text), "object_markers=" + str(text.count("[object Object]"))) PYTHON ``` ### Expected behavior Markdown should preserve the archive entry facts, including `alpha.txt` and `sub/beta.txt`, and their association with the inventory. No summary, filter, or token window was requested. `inspect-artifact --help` advertises all available observations and complete Evidence by default, and advertises Markdown as an output format. `docs/cli.md:3-5` states CLI/MCP workflow and Evidence parity. `docs/tool-design.md:51-56` requires requested facts inline with access to complete Evidence. ### Impact and scope The advertised Markdown representation silently omits requested inventory facts despite successful exit status. Selecting `--format json` is the directly verified workaround. The verifier also observed 31 `[object Object]` occurrences on eight lines from `capabilities --format md`; its JSON control returned four structured capability records in that environment. Observed scope is nested values in object-array table rows and these archive/capability cases. This does not establish that every nested object loses its contents. No source data loss or security boundary crossing was observed. Broader input and platform coverage was not tested. ### REA version 6.7.0, source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`, with lockfile-pinned Incur 0.6.1. ### Runtime and platform Node.js v26.12.0; macOS, Darwin arm64. No Hopper or Ghidra provider was required. ### Target and artifact context A generated ZIP containing `alpha.txt` with `hello\n` and `sub/beta.txt` with `world\n`. No private target data is involved. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence The executed archive comparison printed: ```text zip.md alpha.txt=False sub/beta.txt=False object_markers=2 zip.json alpha.txt=True sub/beta.txt=True object_markers=0 ``` The JSON control retains both names under `normalized_result.substeps[0].evidence.normalized_result.occurrences`. The following standalone Incur CLI probe was also executed from the checkout with its pinned dependency: ```sh node --input-type=module --eval 'import { Cli } from "incur"; await Cli.create("probe").command("rows", { run() { return [{ details: { path: "alpha.txt" }, entries: [{ path: "sub/beta.txt" }] }]; } }).serve(["rows", "--format", "md"]);' ``` It exits 0 and prints: ```text | details | entries | |-----------------|-----------------| | [object Object] | [object Object] | ``` ### Typed outcome or recovery expected A successful Markdown result should retain the nested Evidence facts. The JSON control should continue to retain both entry names. Suppressing the affected columns would still omit requested facts. ### Analysis or suspected cause Source-derived reasoning, corroborated by the standalone reproduction: `src/cli/cliResultOutput.ts:241-245` delegates successful non-JSON rendering to Incur. The bundled `node_modules/incur/dist/Cli.js:1121-1129` calls its formatter. `node_modules/incur/src/Formatter.ts:73-79`, compiled at `dist/Formatter.js:63-65`, applies `String(...)` to object-array table cells. The producing formatter belongs to upstream [wevm/incur](https://github.com/wevm/incur). The choice of remedy is left to REA's maintainers, such as flattening these values in REA, pinning or patching Incur, or documenting the limitation. ### Additional context Prepared with AI assistance and independently reproduced through the public CLI before submission. The standalone Incur probe was also replayed during submission preparation. A fresh fetch through `c374c2fe7290b914d73d9e27af38b498753ec593` found unchanged dependency pins and no later fix for this successful Markdown path. The subsequent CLI change preserves typed failures under filters and still delegates successful non-JSON results to Incur. This upstream check is source-derived; the REA runtime reproduction targets the baseline above. NOT_ENOUGH_INFO: the last known good version and first affected version have not been established.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #5110 and siblings #5448, #5530, #5531, and #5532 regarding Markdown serialization of nested inventory objects in CLI inspection output. Cataloged under Cluster AQ in consolidation tracker #4330.

<!-- gh-comment-id:6293048406 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #5110 and siblings #5448, #5530, #5531, and #5532 regarding Markdown serialization of nested inventory objects in CLI inspection output. Cataloged under Cluster AQ in consolidation tracker #4330.
Author
Owner

@hifzabuildsai commented on GitHub (Oct 20, 2026):

I’d like to work on this. The standalone reproduction points to Incur’s Markdown formatter, so before implementing: would you prefer an REA-local normalization layer with focused CLI regression coverage, or should this be fixed upstream in Incur and consumed through a dependency update? I’ll keep the change bounded to preserving nested Markdown facts and the existing JSON behavior.

<!-- gh-comment-id:6297891874 --> @hifzabuildsai commented on GitHub (Oct 20, 2026): I’d like to work on this. The standalone reproduction points to Incur’s Markdown formatter, so before implementing: would you prefer an REA-local normalization layer with focused CLI regression coverage, or should this be fixed upstream in Incur and consumed through a dependency update? I’ll keep the change bounded to preserving nested Markdown facts and the existing JSON behavior.
Author
Owner

@CassieFox23 commented on GitHub (Oct 20, 2026):

TL;DR: This still happens on main on Linux. inspect-artifact --format md on a small zip prints [object Object] in nested cells. Neither entry name appears, but the JSON output has both.

Checked on main b3aadaf47 (Node 24.18.0). A zip with alpha.txt and sub/beta.txt exits 0 in both formats. The md output has two [object Object] cells, in the inventory_artifact row and the root-manifest row, and no entry names. The json output has both names.

Not run

  • The Incur 0.6.1 root-cause claim, other tools' md output, MCP.

Thanks, coygeek, for the clear report.

Commands
python3 -I mkzip.py   # writes alpha.txt and sub/beta.txt into <case_dir>/fixture.zip
node scripts/rea.mjs inspect-artifact <case_dir>/fixture.zip --format md > zip.md
node scripts/rea.mjs inspect-artifact <case_dir>/fixture.zip --format json > zip.json

Counts from grep:

zip.md   alpha.txt=0 sub/beta.txt=0 [object Object]=2
zip.json alpha.txt=1 sub/beta.txt=1 [object Object]=0

Two md rows, shortened:

| ais_e0c0... | inventory_artifact | completed | ev_ca77... | [object Object] |
| aio_ebe4... | root-manifest | art_40d7... | [object Object] | ev_ca77... |

Isolated HOME, XDG and TMPDIR; run once per format.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.11
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 24.18.0
  • Head: b3aadaf47

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6298148572 --> @CassieFox23 commented on GitHub (Oct 20, 2026): **TL;DR:** This still happens on main on Linux. `inspect-artifact --format md` on a small zip prints `[object Object]` in nested cells. Neither entry name appears, but the JSON output has both. Checked on main b3aadaf47 (Node 24.18.0). A zip with `alpha.txt` and `sub/beta.txt` exits 0 in both formats. The md output has two `[object Object]` cells, in the `inventory_artifact` row and the `root-manifest` row, and no entry names. The json output has both names. ### Not run - The Incur 0.6.1 root-cause claim, other tools' md output, MCP. Thanks, coygeek, for the clear report. <details> <summary>Commands</summary> ```sh python3 -I mkzip.py # writes alpha.txt and sub/beta.txt into <case_dir>/fixture.zip node scripts/rea.mjs inspect-artifact <case_dir>/fixture.zip --format md > zip.md node scripts/rea.mjs inspect-artifact <case_dir>/fixture.zip --format json > zip.json ``` Counts from `grep`: ``` zip.md alpha.txt=0 sub/beta.txt=0 [object Object]=2 zip.json alpha.txt=1 sub/beta.txt=1 [object Object]=0 ``` Two md rows, shortened: ``` | ais_e0c0... | inventory_artifact | completed | ev_ca77... | [object Object] | | aio_ebe4... | root-manifest | art_40d7... | [object Object] | ev_ca77... | ``` Isolated HOME, XDG and TMPDIR; run once per format. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.11 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 24.18.0 - Head: b3aadaf47 </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

REA now owns Markdown formatting and serializes nested table cells as JSON rather than String(object). Successful Markdown output is explicitly retained in the REA result writer instead of delegated to Incur. A public-workspace CLI regression creates exactly alpha.txt and sub/beta.txt, checks both names in Markdown and JSON, checks exit 0 for both, and forbids [object Object] in Markdown.

Implementation: cliOutputFormatter.ts, cliOutputFormatter.ts.

The relevant regression checks in markdownNestedFacts.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380704451 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. REA now owns Markdown formatting and serializes nested table cells as JSON rather than String(object). Successful Markdown output is explicitly retained in the REA result writer instead of delegated to Incur. A public-workspace CLI regression creates exactly alpha.txt and sub/beta.txt, checks both names in Markdown and JSON, checks exit 0 for both, and forbids [object Object] in Markdown. Implementation: [cliOutputFormatter.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/cli/cliOutputFormatter.ts#L25), [cliOutputFormatter.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/cli/cliOutputFormatter.ts#L80). The relevant regression checks in [markdownNestedFacts.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/cli/markdownNestedFacts.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#3361
No description provided.