[GH-ISSUE #5759] [Bug] Rizin debugger TTY preflight errors omit the rejected field and failure reason #3501

Closed
opened 2026-10-31 00:41:12 +00:00 by bsakaguchi · 2 comments
Owner

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

Before submitting

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

Area

Native analysis or decompilation

Observed behavior

On Linux, debug-with-rizin and MCP start_rizin_debug_session reject a missing terminal path, /dev/null, and a regular file with the same generic invalid_request response. It omits the rejected inferior_tty field, selected path, and known failure reason, then tells the caller to correct listed fields although none are listed. An independent verifier reproduced each selection once through CLI and once through a real stdio MCP server. All CLI calls exited 1; all MCP calls returned isError: true. Rejection occurs before Rizin launches.

Reproduction

The verifier executed this block from a built source checkout on Linux with GCC, Node.js, and Rizin on PATH, using disposable fixture/profile directories:

work_dir="$(mktemp -d)"
S="$work_dir"
mkdir -p "$S/home" "$S/cfg" "$S/data" "$S/cache" "$S/state" "$S/tmp"
rizin_command="$(command -v rizin)"
cat > "$S/program.c" <<'C'
int main(void) { return 0; }
C
gcc -g -o "$S/program" "$S/program.c"
rea_isolated() {
  env -i PATH="$PATH" HOME="$S/home" \
    XDG_CONFIG_HOME="$S/cfg" XDG_DATA_HOME="$S/data" \
    XDG_CACHE_HOME="$S/cache" XDG_STATE_HOME="$S/state" TMPDIR="$S/tmp" \
    REA_LOG_LEVEL=silent REA_RIZIN_COMMAND="$rizin_command" \
    node scripts/rea.mjs "$@"
}
for tty_path in "$S/missing-tty" /dev/null "$S/program"; do
  rea_isolated debug-with-rizin "$S/program" iI \
    --inferior-tty "$tty_path" --json
  printf 'exit=%s\n' "$?"
done

For MCP, the verifier called start_rizin_debug_session on a fresh stdio server with path set to the generated executable's absolute path and inferior_tty set to each of the same three values. The projected error was equivalent inside the different CLI/MCP response envelopes.

Expected behavior

Keep unsuitable selections rejected while identifying the selected terminal argument/path and preserving a meaningful cause and correction. A missing path and an existing non-TTY require different corrective actions. No particular replacement error code or object shape is required.

Source-derived contract: AGENTS.md, Product and Authority, requires preserving caller-selected paths; Architecture and Cleanup requires preserving meaningful failure reasons. docs/reverse-engineering-tools.md:46-48 and src/contracts/rizinDebugToolContracts.ts:22-30 establish the explicit Linux TTY requirement. References are at the baseline below.

Impact and scope

P2: this bounded diagnostic/recovery defect prevents callers from distinguishing a stale terminal path from a non-TTY selection using the response. All three rejected inputs reproduced through both interfaces; correct rejection remains intact. No security boundary or target-execution failure is claimed. The demonstrated workaround is independent existence/open/isatty inspection and selection of a fresh PTY. Character-device status alone is insufficient: /dev/null is a character device but is not a TTY. Permission-denial inputs were not tested.

REA version

rea-agents 6.7.0, source commit 9d8e7803326a382cbbb3ffdb900f5e93e5442d68, built runtime. Rechecked current main by fetching origin/main at d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f: no intervening commits changed the cited contract, TTY preflight, CLI/MCP registration, or error core/presentation/projection files. No additional runtime rerun on main was performed.

Runtime and platform

Reproduction: Node.js v24.22.0; Debian 12 Linux amd64 container under emulation; Rizin 0.9.1; GCC 12.2.0. No platform gate was overridden. Submission host only: node --version v26.12.0; sw_vers -productVersion 27.0.1; uname -m arm64. The Linux result is not a claim of macOS debugger support.

Target and artifact context

Self-built ELF64 executable from the one-line C program above. TTY selections: an absent path, /dev/null, and that regular executable file. REA used isolated HOME/XDG/temporary directories and silent logging. Direct static Rizin inspection of the executable succeeded.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

All three CLI selections produced this identical stdout with exit 1 and empty stderr:

{"code":"invalid_request","category":"invalid_input","message":"Analysis input is invalid. Check the arguments and try again.","retryable":false,"remediation":{"action":"Read the declared input schema or CLI usage for start_rizin_debug_session; if using MCP and the catalog may be stale, call tools/list. Correct the listed fields, then retry. Do not repeat the unchanged call."},"recovery":{"kind":"inspect_input_contract","operation":"start_rizin_debug_session"}}

There are no details, field issues, selected terminal path, or underlying reason. The MCP calls returned the same error under error in text content with isError: true.

Independent filesystem/terminal checks observed ENOENT for the missing path and isatty=false for both existing non-TTY selections. A fresh PTY had isatty=true and passed REA's preflight: an intentionally nonexistent provider then reached spawn and returned its ENOENT reason. Evidence provenance: observed; local verification: independently reproduced.

Typed outcome or recovery expected

Retain a typed rejection that names inferior_tty, preserves its selected path, distinguishes missing-path from non-terminal causes, and explains how to correct that selection. Keep /dev/null rejected. A fresh live PTY should continue to pass preflight and reach provider spawn; successful live debugging requires a separate working-ptrace control.

Analysis or suspected cause

Source-derived reasoning: src/cli/reverseEngineeringCommands.ts:330-375 and src/server/registerRizinDebugTools.ts:32-69 share src/rizin/RizinDebugSessionManager.ts:320-357. Its realpath/open/isatty/stat catch wraps the failure in AnalysisInputError with only cause. src/domain/analysisErrorCore.ts:65-79 defaults field issues to empty; src/domain/analysisErrorPresentation.ts:133-134,253-257 supplies generic input advice; src/domain/analysisErrorProjection.ts:332-345 emits input details only for populated issues. The producer has the meaningful cause, but the public projection loses it. The documented generic fallback does not supply those runtime facts.

Additional context

Prepared with AI assistance and independently verified. Related #5401 concerns objdump target-file acquisition diagnostics in a different producer; this report covers Rizin debugger terminal admission and its CLI/MCP restoration signal. #5099 concerns GDB verification revocation after read-only queries; #5090 concerns Rizin cleanup exit status. They do not cover this preflight response.

Direct Rizin debugging with production startup arguments emitted ptrace register I/O errors under emulation. A successful live REA session executing iI was therefore not established; the verified diagnostic loss precedes provider spawn. NOT_ENOUGH_INFO: last known good version and introducing commit. No fix or restoration is claimed.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5759 ### 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 Native analysis or decompilation ### Observed behavior On Linux, `debug-with-rizin` and MCP `start_rizin_debug_session` reject a missing terminal path, `/dev/null`, and a regular file with the same generic `invalid_request` response. It omits the rejected `inferior_tty` field, selected path, and known failure reason, then tells the caller to correct listed fields although none are listed. An independent verifier reproduced each selection once through CLI and once through a real stdio MCP server. All CLI calls exited 1; all MCP calls returned `isError: true`. Rejection occurs before Rizin launches. ### Reproduction The verifier executed this block from a built source checkout on Linux with GCC, Node.js, and Rizin on PATH, using disposable fixture/profile directories: ```sh work_dir="$(mktemp -d)" S="$work_dir" mkdir -p "$S/home" "$S/cfg" "$S/data" "$S/cache" "$S/state" "$S/tmp" rizin_command="$(command -v rizin)" cat > "$S/program.c" <<'C' int main(void) { return 0; } C gcc -g -o "$S/program" "$S/program.c" rea_isolated() { env -i PATH="$PATH" HOME="$S/home" \ XDG_CONFIG_HOME="$S/cfg" XDG_DATA_HOME="$S/data" \ XDG_CACHE_HOME="$S/cache" XDG_STATE_HOME="$S/state" TMPDIR="$S/tmp" \ REA_LOG_LEVEL=silent REA_RIZIN_COMMAND="$rizin_command" \ node scripts/rea.mjs "$@" } for tty_path in "$S/missing-tty" /dev/null "$S/program"; do rea_isolated debug-with-rizin "$S/program" iI \ --inferior-tty "$tty_path" --json printf 'exit=%s\n' "$?" done ``` For MCP, the verifier called `start_rizin_debug_session` on a fresh stdio server with `path` set to the generated executable's absolute path and `inferior_tty` set to each of the same three values. The projected error was equivalent inside the different CLI/MCP response envelopes. ### Expected behavior Keep unsuitable selections rejected while identifying the selected terminal argument/path and preserving a meaningful cause and correction. A missing path and an existing non-TTY require different corrective actions. No particular replacement error code or object shape is required. Source-derived contract: `AGENTS.md`, Product and Authority, requires preserving caller-selected paths; Architecture and Cleanup requires preserving meaningful failure reasons. `docs/reverse-engineering-tools.md:46-48` and `src/contracts/rizinDebugToolContracts.ts:22-30` establish the explicit Linux TTY requirement. References are at the baseline below. ### Impact and scope P2: this bounded diagnostic/recovery defect prevents callers from distinguishing a stale terminal path from a non-TTY selection using the response. All three rejected inputs reproduced through both interfaces; correct rejection remains intact. No security boundary or target-execution failure is claimed. The demonstrated workaround is independent existence/open/isatty inspection and selection of a fresh PTY. Character-device status alone is insufficient: `/dev/null` is a character device but is not a TTY. Permission-denial inputs were not tested. ### REA version `rea-agents` 6.7.0, source commit `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`, built runtime. Rechecked current main by fetching `origin/main` at `d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f`: no intervening commits changed the cited contract, TTY preflight, CLI/MCP registration, or error core/presentation/projection files. No additional runtime rerun on main was performed. ### Runtime and platform Reproduction: Node.js v24.22.0; Debian 12 Linux amd64 container under emulation; Rizin 0.9.1; GCC 12.2.0. No platform gate was overridden. Submission host only: `node --version` v26.12.0; `sw_vers -productVersion` 27.0.1; `uname -m` arm64. The Linux result is not a claim of macOS debugger support. ### Target and artifact context Self-built ELF64 executable from the one-line C program above. TTY selections: an absent path, `/dev/null`, and that regular executable file. REA used isolated HOME/XDG/temporary directories and silent logging. Direct static Rizin inspection of the executable succeeded. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence All three CLI selections produced this identical stdout with exit 1 and empty stderr: ```json {"code":"invalid_request","category":"invalid_input","message":"Analysis input is invalid. Check the arguments and try again.","retryable":false,"remediation":{"action":"Read the declared input schema or CLI usage for start_rizin_debug_session; if using MCP and the catalog may be stale, call tools/list. Correct the listed fields, then retry. Do not repeat the unchanged call."},"recovery":{"kind":"inspect_input_contract","operation":"start_rizin_debug_session"}} ``` There are no `details`, field issues, selected terminal path, or underlying reason. The MCP calls returned the same error under `error` in text content with `isError: true`. Independent filesystem/terminal checks observed ENOENT for the missing path and `isatty=false` for both existing non-TTY selections. A fresh PTY had `isatty=true` and passed REA's preflight: an intentionally nonexistent provider then reached spawn and returned its ENOENT reason. Evidence provenance: observed; local verification: independently reproduced. ### Typed outcome or recovery expected Retain a typed rejection that names `inferior_tty`, preserves its selected path, distinguishes missing-path from non-terminal causes, and explains how to correct that selection. Keep `/dev/null` rejected. A fresh live PTY should continue to pass preflight and reach provider spawn; successful live debugging requires a separate working-ptrace control. ### Analysis or suspected cause Source-derived reasoning: `src/cli/reverseEngineeringCommands.ts:330-375` and `src/server/registerRizinDebugTools.ts:32-69` share `src/rizin/RizinDebugSessionManager.ts:320-357`. Its realpath/open/isatty/stat catch wraps the failure in `AnalysisInputError` with only `cause`. `src/domain/analysisErrorCore.ts:65-79` defaults field issues to empty; `src/domain/analysisErrorPresentation.ts:133-134,253-257` supplies generic input advice; `src/domain/analysisErrorProjection.ts:332-345` emits input details only for populated issues. The producer has the meaningful cause, but the public projection loses it. The documented generic fallback does not supply those runtime facts. ### Additional context Prepared with AI assistance and independently verified. Related #5401 concerns objdump target-file acquisition diagnostics in a different producer; this report covers Rizin debugger terminal admission and its CLI/MCP restoration signal. #5099 concerns GDB verification revocation after read-only queries; #5090 concerns Rizin cleanup exit status. They do not cover this preflight response. Direct Rizin debugging with production startup arguments emitted ptrace register I/O errors under emulation. A successful live REA session executing `iI` was therefore not established; the verified diagnostic loss precedes provider spawn. NOT_ENOUGH_INFO: last known good version and introducing commit. No fix or restoration is claimed.
Author
Owner

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

This matches the pattern in #5401/#5099/#5090 — a single generic invalid_request that names no field makes the three cases indistinguishable from the caller's side. Agree the rejection itself should stay; what's missing is attribution and cause. Since the TTY requirement is explicit in the rizin tool contract (inferior_tty must be an existing TTY), the preflight already has everything needed to say which condition failed: nonexistent path, exists-but-not-a-TTY (including /dev/null — character device but not a TTY), or permission. Emitting the evaluated path plus the specific condition and its standard corrective action would let CLI and MCP callers fix the selection without an external isatty probe. Also worth keeping the "correct the listed fields" wording only when it actually lists the fields.

<!-- gh-comment-id:6307800698 --> @XinwangTao commented on GitHub (Oct 21, 2026): This matches the pattern in #5401/#5099/#5090 — a single generic invalid_request that names no field makes the three cases indistinguishable from the caller's side. Agree the rejection itself should stay; what's missing is attribution and cause. Since the TTY requirement is explicit in the rizin tool contract (inferior_tty must be an existing TTY), the preflight already has everything needed to say which condition failed: nonexistent path, exists-but-not-a-TTY (including /dev/null — character device but not a TTY), or permission. Emitting the evaluated path plus the specific condition and its standard corrective action would let CLI and MCP callers fix the selection without an external isatty probe. Also worth keeping the "correct the listed fields" wording only when it actually lists the fields.
Author
Owner

@morluto commented on GitHub (Oct 23, 2026):

Resolved by github.com/morluto/rea@171b33b2c4. Selected TTY validation returns field/path and physical failure before Rizin spawn. The moved regression is owned by the existing provider-boundary project and actually executes. Validation: tests/boundary/providers/RizinDebugSessionAdmission.test.ts passed in the recorded round2/3 execution; this review did not rerun tests. Coverage is limited to the cited control; no broader real-provider, Windows or performance claim.

<!-- gh-comment-id:6376114536 --> @morluto commented on GitHub (Oct 23, 2026): Resolved by https://github.com/morluto/rea/commit/171b33b2c4defc6cbea49545bedf4272b72d1ea2. Selected TTY validation returns field/path and physical failure before Rizin spawn. The moved regression is owned by the existing provider-boundary project and actually executes. Validation: `tests/boundary/providers/RizinDebugSessionAdmission.test.ts` passed in the recorded round2/3 execution; this review did not rerun tests. Coverage is limited to the cited control; no broader real-provider, Windows or performance claim.
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#3501
No description provided.