[GH-ISSUE #7691] [Bug] Unknown MCP subcommands emit TOON after selecting JSON or Markdown #4613

Open
opened 2026-10-31 00:51:21 +00:00 by bsakaguchi · 1 comment
Owner

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

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 mcp bogus --format json rejects the unknown command with exit 1 but writes TOON, which fails JSON decoding. Selecting --format yaml, --format md, or --json produces exactly the same 196 stdout bytes. Stderr is empty.

With --format jsonl, the error envelope parses, but its result is the already-rendered TOON string. The root control, node scripts/rea.mjs bogus --format json, returns a JSON object with code: "COMMAND_NOT_FOUND" and exit 1.

Reproduction

  1. Use the baseline below with its locked dependencies, compiled runtime, and Node.js v24.22.1 selected on PATH. Verification used the existing build; dependency installation and rebuilding were not repeated.
  2. From the checkout root, execute this isolated reproduction. Its runner fixtures prevent package downloads, and its temporary files are removed automatically.
python3 -I - <<'PYREPRO'
import hashlib, json, os, pathlib, subprocess, tempfile

cases = [
    ('json', ['mcp', 'bogus', '--format', 'json']),
    ('yaml', ['mcp', 'bogus', '--format', 'yaml']),
    ('md', ['mcp', 'bogus', '--format', 'md']),
    ('alias', ['mcp', 'bogus', '--json']),
    ('jsonl', ['mcp', 'bogus', '--format', 'jsonl']),
    ('root', ['bogus', '--format', 'json']),
]
with tempfile.TemporaryDirectory() as work:
    root = pathlib.Path(work)
    runners = root / 'bin'
    runners.mkdir()
    for name in ('npx', 'npm', 'bun'):
        fixture = runners / name
        fixture.write_text('#!/bin/sh\nprintf "%s\\n" called >> "$RUNNER_LOG"\nexit 97\n')
        fixture.chmod(0o755)
    env = {k: v for k, v in os.environ.items() if not k.startswith('REA_')}
    env['PATH'] = str(runners) + os.pathsep + env['PATH']
    env['RUNNER_LOG'] = str(root / 'runner.log')
    for key, directory in (
        ('HOME', 'home'), ('XDG_CONFIG_HOME', 'config'),
        ('XDG_DATA_HOME', 'data'), ('XDG_CACHE_HOME', 'cache'),
        ('XDG_STATE_HOME', 'state'), ('TMPDIR', 'temp'),
    ):
        path = root / directory
        path.mkdir()
        env[key] = str(path)
    for label, args in cases:
        captures = []
        for attempt in range(3):
            result = subprocess.run(['node', 'scripts/rea.mjs', *args],
                env=env, capture_output=True, timeout=30)
            captures.append(result.stdout)
            print(label, attempt + 1, 'exit', result.returncode,
                  'stdout', len(result.stdout), 'stderr', len(result.stderr),
                  'sha256', hashlib.sha256(result.stdout).hexdigest())
        assert captures[0] == captures[1] == captures[2]
        try:
            value = json.loads(captures[0])
            print('JSON parses; result type:', type(value.get('result')).__name__)
        except ValueError:
            print('JSON does not parse')
        print(captures[0].decode())
    print('runner invoked:', (root / 'runner.log').exists())
    help_result = subprocess.run(['node', 'scripts/rea.mjs', 'mcp', '--help'],
        env=env, capture_output=True, timeout=30)
    print(help_result.stdout.decode())
PYREPRO
  1. Each MCP JSON/YAML/Markdown/alias case returns 196 identical TOON bytes; JSONL returns 262 bytes; the root JSON control returns 268 bytes. Every error invocation exits 1 with zero stderr bytes. Each case reproduced three times, and the complete inline procedure was replayed during verification. The runner log remains absent.

Expected behavior

An unknown subcommand should still fail, while its diagnostic uses the selected output format. JSON selection should yield a parseable diagnostic document, and Markdown selection should use the advertised Markdown representation.

The CLI guide recommends --json for JSON consumers and describes global output selection without an exception for unknown builtin subcommands. Executed rea mcp --help advertises --format <toon|json|yaml|md|jsonl> under Global Options. The root unknown-command control already respects JSON selection.

Impact and scope

P2: a JSON consumer cannot decode the error document for a misspelled MCP subcommand, despite explicitly selecting JSON. The failure status and human-readable diagnostic remain intact. No downstream application failure, data loss, or successful MCP operation was tested.

A bounded workaround was observed: JSONL produces a valid error envelope whose string result retains the diagnostic text, including code: COMMAND_NOT_FOUND. Consumers must handle that text rather than object fields. The tested scope is the unknown-subcommand path on the platform below; other builtin failures and platforms are unverified.

REA version

Exact node scripts/rea.mjs --version output:

6.8.0

Source commit: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; darwin arm64; locked Incur 0.6.1. No analysis provider or real MCP client was started.

Target and artifact context

No analysis target. Only disposable HOME/XDG directories and local runner fixtures were used.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

Observed stdout for the JSON, YAML, Markdown, and alias variants:

code: COMMAND_NOT_FOUND
message: 'bogus' is not a command for 'rea mcp'.
cta:
  description: "Suggested command:"
  commands[1]{command,description}:
    rea mcp --help,see all available commands

All four captures have SHA-256 fc5492c3902489d571467889765327eb9650be498f9e115906240265ef8d70db. JSON decoding fails at the first character for the JSON and alias cases. JSONL parses as an error record with $rea: "jsonl/v1", ok: false, and a string result containing this text. The root JSON control parses with code: "COMMAND_NOT_FOUND".

Baseline locators:

  • docs/cli.md:954-963: JSON-consumer guidance and global output options; :965 onward describes Markdown rendering.
  • docs/cli.md:128-130: JSONL error-envelope and diagnostic-preservation guidance. This does not separately establish an object-only result schema for SDK diagnostics.
  • src/cli.ts:95-103: MCP integration configuration.
  • node_modules/incur/src/Cli.ts:1239-1266: builtin unknown-subcommand dispatch explicitly selects TOON for this error.
  • scripts/rea.mjs:139-150: forwarding of SDK stdout; src/cli/cliResultOutput.ts:233-270: JSONL capture and preservation of non-JSON diagnostic text.

Typed outcome or recovery expected

Preserve the unknown-command rejection and useful diagnostic fields while making complete stdout match the requested document format. A JSON consumer should not need to parse TOON first.

Analysis or suspected cause

Source-derived: pinned Incur renders this builtin error before REA's registered-command result wrappers can format it. REA forwards the resulting text. Its JSONL adapter cannot decode TOON as JSON, so it preserves that text as the error record's string result.

An independently executed standalone Incur probe, without REA's MCP configuration, also emitted TOON for the equivalent JSON request. The producing branch is upstream framework code. This report concerns the public REA format contract exposed by its shipped CLI; it does not claim REA authored that branch or prescribe whether restoration belongs in an adapter or dependency change.

Additional context

Open/closed issue searches, all-state PR searches including merged work, and issue-comment searches found no same-defect report. Related reports have distinct closure signals:

  • PR #2693 changes only production doctor parsing and its tests; it does not intercept an unknown MCP group subcommand. #6455 and merged PR #6489 concern missing format values on doctor.
  • #7610 concerns human prefixes on successful registration. #7375, #7608, and #7609 concern registration argument validation and side effects.
  • #7440 concerns Markdown escaping, and #7441 concerns indentation of valid JSON. Closed #5531 concerns malformed output values rather than a valid selector accompanying an unknown command.

The first affected release is unknown. No repair or restoration was tested.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Repeated unknown MCP subcommand requests emitted non-JSON stdout after selecting JSON, preventing complete-document decoding while preserving exit 1 and the diagnostic text.
  • Workaround status: available
  • Workaround basis: The tested JSONL option yields a parseable error envelope containing the complete diagnostic as text; it requires string handling and does not restore object fields.
Originally created by @coygeek on GitHub (Oct 28, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7691 ### 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 mcp bogus --format json` rejects the unknown command with exit 1 but writes TOON, which fails JSON decoding. Selecting `--format yaml`, `--format md`, or `--json` produces exactly the same 196 stdout bytes. Stderr is empty. With `--format jsonl`, the error envelope parses, but its `result` is the already-rendered TOON string. The root control, `node scripts/rea.mjs bogus --format json`, returns a JSON object with `code: "COMMAND_NOT_FOUND"` and exit 1. ### Reproduction 1. Use the baseline below with its locked dependencies, compiled runtime, and Node.js v24.22.1 selected on PATH. Verification used the existing build; dependency installation and rebuilding were not repeated. 2. From the checkout root, execute this isolated reproduction. Its runner fixtures prevent package downloads, and its temporary files are removed automatically. ```sh python3 -I - <<'PYREPRO' import hashlib, json, os, pathlib, subprocess, tempfile cases = [ ('json', ['mcp', 'bogus', '--format', 'json']), ('yaml', ['mcp', 'bogus', '--format', 'yaml']), ('md', ['mcp', 'bogus', '--format', 'md']), ('alias', ['mcp', 'bogus', '--json']), ('jsonl', ['mcp', 'bogus', '--format', 'jsonl']), ('root', ['bogus', '--format', 'json']), ] with tempfile.TemporaryDirectory() as work: root = pathlib.Path(work) runners = root / 'bin' runners.mkdir() for name in ('npx', 'npm', 'bun'): fixture = runners / name fixture.write_text('#!/bin/sh\nprintf "%s\\n" called >> "$RUNNER_LOG"\nexit 97\n') fixture.chmod(0o755) env = {k: v for k, v in os.environ.items() if not k.startswith('REA_')} env['PATH'] = str(runners) + os.pathsep + env['PATH'] env['RUNNER_LOG'] = str(root / 'runner.log') for key, directory in ( ('HOME', 'home'), ('XDG_CONFIG_HOME', 'config'), ('XDG_DATA_HOME', 'data'), ('XDG_CACHE_HOME', 'cache'), ('XDG_STATE_HOME', 'state'), ('TMPDIR', 'temp'), ): path = root / directory path.mkdir() env[key] = str(path) for label, args in cases: captures = [] for attempt in range(3): result = subprocess.run(['node', 'scripts/rea.mjs', *args], env=env, capture_output=True, timeout=30) captures.append(result.stdout) print(label, attempt + 1, 'exit', result.returncode, 'stdout', len(result.stdout), 'stderr', len(result.stderr), 'sha256', hashlib.sha256(result.stdout).hexdigest()) assert captures[0] == captures[1] == captures[2] try: value = json.loads(captures[0]) print('JSON parses; result type:', type(value.get('result')).__name__) except ValueError: print('JSON does not parse') print(captures[0].decode()) print('runner invoked:', (root / 'runner.log').exists()) help_result = subprocess.run(['node', 'scripts/rea.mjs', 'mcp', '--help'], env=env, capture_output=True, timeout=30) print(help_result.stdout.decode()) PYREPRO ``` 3. Each MCP JSON/YAML/Markdown/alias case returns 196 identical TOON bytes; JSONL returns 262 bytes; the root JSON control returns 268 bytes. Every error invocation exits 1 with zero stderr bytes. Each case reproduced three times, and the complete inline procedure was replayed during verification. The runner log remains absent. ### Expected behavior An unknown subcommand should still fail, while its diagnostic uses the selected output format. JSON selection should yield a parseable diagnostic document, and Markdown selection should use the advertised Markdown representation. The CLI guide recommends `--json` for JSON consumers and describes global output selection without an exception for unknown builtin subcommands. Executed `rea mcp --help` advertises `--format <toon|json|yaml|md|jsonl>` under Global Options. The root unknown-command control already respects JSON selection. ### Impact and scope P2: a JSON consumer cannot decode the error document for a misspelled MCP subcommand, despite explicitly selecting JSON. The failure status and human-readable diagnostic remain intact. No downstream application failure, data loss, or successful MCP operation was tested. A bounded workaround was observed: JSONL produces a valid error envelope whose string result retains the diagnostic text, including `code: COMMAND_NOT_FOUND`. Consumers must handle that text rather than object fields. The tested scope is the unknown-subcommand path on the platform below; other builtin failures and platforms are unverified. ### REA version Exact `node scripts/rea.mjs --version` output: ```text 6.8.0 ``` Source commit: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js v24.22.1; darwin arm64; locked Incur 0.6.1. No analysis provider or real MCP client was started. ### Target and artifact context No analysis target. Only disposable HOME/XDG directories and local runner fixtures were used. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence Observed stdout for the JSON, YAML, Markdown, and alias variants: ```text code: COMMAND_NOT_FOUND message: 'bogus' is not a command for 'rea mcp'. cta: description: "Suggested command:" commands[1]{command,description}: rea mcp --help,see all available commands ``` All four captures have SHA-256 `fc5492c3902489d571467889765327eb9650be498f9e115906240265ef8d70db`. JSON decoding fails at the first character for the JSON and alias cases. JSONL parses as an error record with `$rea: "jsonl/v1"`, `ok: false`, and a string result containing this text. The root JSON control parses with `code: "COMMAND_NOT_FOUND"`. Baseline locators: - `docs/cli.md:954-963`: JSON-consumer guidance and global output options; `:965` onward describes Markdown rendering. - `docs/cli.md:128-130`: JSONL error-envelope and diagnostic-preservation guidance. This does not separately establish an object-only result schema for SDK diagnostics. - `src/cli.ts:95-103`: MCP integration configuration. - `node_modules/incur/src/Cli.ts:1239-1266`: builtin unknown-subcommand dispatch explicitly selects TOON for this error. - `scripts/rea.mjs:139-150`: forwarding of SDK stdout; `src/cli/cliResultOutput.ts:233-270`: JSONL capture and preservation of non-JSON diagnostic text. ### Typed outcome or recovery expected Preserve the unknown-command rejection and useful diagnostic fields while making complete stdout match the requested document format. A JSON consumer should not need to parse TOON first. ### Analysis or suspected cause Source-derived: pinned Incur renders this builtin error before REA's registered-command result wrappers can format it. REA forwards the resulting text. Its JSONL adapter cannot decode TOON as JSON, so it preserves that text as the error record's string result. An independently executed standalone Incur probe, without REA's MCP configuration, also emitted TOON for the equivalent JSON request. The producing branch is upstream framework code. This report concerns the public REA format contract exposed by its shipped CLI; it does not claim REA authored that branch or prescribe whether restoration belongs in an adapter or dependency change. ### Additional context Open/closed issue searches, all-state PR searches including merged work, and issue-comment searches found no same-defect report. Related reports have distinct closure signals: - [PR #2693](https://github.com/morluto/rea/pull/2693) changes only production doctor parsing and its tests; it does not intercept an unknown MCP group subcommand. [#6455](https://github.com/morluto/rea/issues/6455) and merged [PR #6489](https://github.com/morluto/rea/pull/6489) concern missing format values on doctor. - [#7610](https://github.com/morluto/rea/issues/7610) concerns human prefixes on successful registration. [#7375](https://github.com/morluto/rea/issues/7375), [#7608](https://github.com/morluto/rea/issues/7608), and [#7609](https://github.com/morluto/rea/issues/7609) concern registration argument validation and side effects. - [#7440](https://github.com/morluto/rea/issues/7440) concerns Markdown escaping, and [#7441](https://github.com/morluto/rea/issues/7441) concerns indentation of valid JSON. Closed [#5531](https://github.com/morluto/rea/issues/5531) concerns malformed output values rather than a valid selector accompanying an unknown command. The first affected release is unknown. No repair or restoration was tested. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Repeated unknown MCP subcommand requests emitted non-JSON stdout after selecting JSON, preventing complete-document decoding while preserving exit 1 and the diagnostic text. - Workaround status: available - Workaround basis: The tested JSONL option yields a parseable error envelope containing the complete diagnostic as text; it requires string handling and does not restore object fields.
Author
Owner

@chordpli commented on GitHub (Oct 28, 2026):

I'd like to take this one.

Reproduced on main at b9a8f8e9f (Node 24.22.1, built CLI):

  • rea mcp bogus --format json / --json / --format md → TOON on stdout, exit 1
  • rea mcp bogus --format jsonl → JSONL envelope whose result is the rendered TOON string
  • rea bogus --format json → JSON, as expected

The mcp (and skills) unknown-subcommand branch in incur's Cli.js hardcodes 'toon' instead of honoring the selected format; incur 0.7.1 is the same. I plan to handle the unknown mcp subcommand on REA's side so the selected format is honored, and I'll report the root cause to incur separately. Will verify before/after with the built CLI.

<!-- gh-comment-id:6470139504 --> @chordpli commented on GitHub (Oct 28, 2026): I'd like to take this one. Reproduced on main at b9a8f8e9f (Node 24.22.1, built CLI): - `rea mcp bogus --format json` / `--json` / `--format md` → TOON on stdout, exit 1 - `rea mcp bogus --format jsonl` → JSONL envelope whose `result` is the rendered TOON string - `rea bogus --format json` → JSON, as expected The `mcp` (and `skills`) unknown-subcommand branch in incur's `Cli.js` hardcodes `'toon'` instead of honoring the selected format; incur 0.7.1 is the same. I plan to handle the unknown `mcp` subcommand on REA's side so the selected format is honored, and I'll report the root cause to incur separately. Will verify before/after with the built CLI.
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#4613
No description provided.