[GH-ISSUE #5761] [Bug] GDB shell commands without a trailing newline time out after completing #3502

Closed
opened 2026-10-31 00:41:14 +00:00 by bsakaguchi · 1 comment
Owner

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

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

node scripts/rea.mjs debug-with-gdb "shell printf abc" --inferior-tty "$tty_path" --json admits the command, but waits for its command deadline and returns cleanup_incomplete even though GDB completed it. The partial raw Evidence retains abc4^done, with completion_status: "unknown". Adding a trailing newline makes the same three-byte shell output complete successfully. This is a completion-correlation failure, not lost payload bytes.

Reproduction

The independent verifier executed each failing input and its newline-terminated control once on the baseline below, with real GDB and isolated profiles. From a built source checkout on Linux with Bash, jq, GDB and util-linux script, run:

work_dir="$(mktemp -d)"
mkdir -p "$work_dir"/{home,cfg,data,cache,state,tmp}
cat > "$work_dir/run-one.sh" <<'SH'
#!/bin/bash
S="$1"
case "$2" in
  fail) command="shell printf abc" ;;
  control) command="shell printf 'abc\n'" ;;
esac
tty_path="$(tty)"
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 node scripts/rea.mjs debug-with-gdb "$command" \
  --inferior-tty "$tty_path" --json > "$S/$2.json" 2> "$S/$2.err"
exit_code=$?
printf '%s exit %s\n' "$2" "$exit_code" >> "$S/exits.txt"
exit "$exit_code"
SH
script -qec "bash '$work_dir/run-one.sh' '$work_dir' fail" /dev/null
script -qec "bash '$work_dir/run-one.sh' '$work_dir' control" /dev/null
cat "$work_dir/exits.txt"
jq '{code, reason: .details.reason,
     partial: .details.partial_observation.raw_result}' "$work_dir/fail.json"
jq '.normalized_result | {records, completion_status, output_truncated}' \
  "$work_dir/control.json"

The failing call takes about two minutes. For comparison, the verifier also sent tokenized requests directly to the same GDB:

printf '%s\n' \
  '1-interpreter-exec console "set auto-load off"' \
  '2-interpreter-exec console "shell printf abc"' \
  '3-gdb-exit' |
  gdb --nx --quiet --interpreter=mi3 > "$work_dir/direct-mi.txt"
tail -4 "$work_dir/direct-mi.txt"

Direct GDB emits abc2^done on one line and exits successfully. No inferior is launched.

Expected behavior

Return completed Evidence with the shell output and a correlated completion result, as the newline-terminated control does. A completed three-byte command should not depend on the shell adding LF. The provider guide explicitly permits unrestricted shell commands and promises raw MI/stream Evidence; gdb_console's contract makes the same unrestricted-command promise.

Impact and scope

P2: admitted commands without a final newline consume the full deadline and return unknown completion plus incomplete cleanup. The three-byte case and a 9,000,000-byte newline-free pipeline both failed; both newline-terminated controls succeeded. Ensuring shell output ends in LF is an observed workaround. Raw payload bytes were retained in these cases. Persistent MCP behavior is source-derived from the shared manager and was not separately reproduced. No security boundary crossing or target execution was involved.

REA version

rea-agents 6.7.0, source commit 9d8e7803326a382cbbb3ffdb900f5e93e5442d68. Submission recheck fetched main at d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f: no commits or content changes since the baseline in docs/reverse-engineering-tools.md, src/contracts/gdbToolContracts.ts, or src/gdb/GdbSessionManager.ts. Runtime reproduction was not rerun for submission.

Runtime and platform

Verified runtime: Node.js v24.22.0; Debian GDB 13.1-3; Linux amd64 container under emulation. REA and direct GDB ran in the same container. Submission host only: Node.js v26.12.0, macOS 27.0.1, arm64; no macOS GDB reproduction is claimed.

Target and artifact context

Caller-selected pseudo-terminal, isolated HOME/XDG/temp directories, and shell stdout. No executable fixture or inferior was started. The source checkout was mounted read-only during verification.

Interface

CLI

Earliest failure stage

Capture or observation

Relevant output or evidence

Independent verifier's observed results:

Input Direct GDB REA
shell printf abc exit 0, 0.171 s exit 1, 121.571 s, cleanup_incomplete
shell printf 'abc\n' exit 0, 0.229 s exit 0, 1.468 s, complete

The failing JSON reason starts GDB command output could not be correlated and its owned process could not be confirmed stopped; underlying execution failure is capability_unavailable. Its partial raw result is:

{
  "mi": "",
  "records": ["abc4^done", "(gdb) "],
  "console": "",
  "target": "",
  "log": "",
  "output_truncated": false,
  "completion_status": "unknown"
}

The passing control has records ["abc", "4^done"], complete status, and no truncation. The verifier also observed the 9,000,000-byte newline-free pipeline fail after 121.506 s with all payload bytes followed immediately by 4^done; piping the same payload through awk '{print}' succeeded in 1.734 s. These comparisons distinguish the missing boundary from generic large-output failure or emulation slowness.

Typed outcome or recovery expected

Completed Evidence preserving the three output bytes and correlated completion. Keep the newline control passing, genuine timeouts explicit, and existing protocol-resource limits intact. Proposed restoration check: repeat both small inputs and the below-budget 9,000,000-byte pair; no fix has been tested.

Analysis or suspected cause

Source-derived explanation: GdbSessionManager.ts:1153 sends MI interpreter-exec console requests. The receiver collects LF-delimited records, then matches completion only at the start of a line. Real GDB emits unframed shell stdout immediately before its token, so abc4^done does not match. The resulting deadline leaves MI synchronization and inferior-state cleanup unknown. This mechanism agrees with the real-provider transcript and passing LF controls; it is not attributed to a bundled dependency.

Additional context

Prepared with AI assistance and independently reproduced by a separate verifier; the submitter inspected the saved small-case outputs, controls, source, and current-main diff. No earlier known-good version was established. A separate oversized MI-line guard is intentional and is outside this report. Related #4015 concerns Rizin losing pending bytes on exit/timeout; this GDB case retains bytes but misses an already emitted completion. The retained-summary feature #5584 and lifecycle PRs #1961/#2799 do not cover this trigger; #1961 retains the anchored completion matcher.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5761 ### 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 `node scripts/rea.mjs debug-with-gdb "shell printf abc" --inferior-tty "$tty_path" --json` admits the command, but waits for its command deadline and returns `cleanup_incomplete` even though GDB completed it. The partial raw Evidence retains `abc4^done`, with `completion_status: "unknown"`. Adding a trailing newline makes the same three-byte shell output complete successfully. This is a completion-correlation failure, not lost payload bytes. ### Reproduction The independent verifier executed each failing input and its newline-terminated control once on the baseline below, with real GDB and isolated profiles. From a built source checkout on Linux with Bash, jq, GDB and util-linux `script`, run: ```bash work_dir="$(mktemp -d)" mkdir -p "$work_dir"/{home,cfg,data,cache,state,tmp} cat > "$work_dir/run-one.sh" <<'SH' #!/bin/bash S="$1" case "$2" in fail) command="shell printf abc" ;; control) command="shell printf 'abc\n'" ;; esac tty_path="$(tty)" 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 node scripts/rea.mjs debug-with-gdb "$command" \ --inferior-tty "$tty_path" --json > "$S/$2.json" 2> "$S/$2.err" exit_code=$? printf '%s exit %s\n' "$2" "$exit_code" >> "$S/exits.txt" exit "$exit_code" SH script -qec "bash '$work_dir/run-one.sh' '$work_dir' fail" /dev/null script -qec "bash '$work_dir/run-one.sh' '$work_dir' control" /dev/null cat "$work_dir/exits.txt" jq '{code, reason: .details.reason, partial: .details.partial_observation.raw_result}' "$work_dir/fail.json" jq '.normalized_result | {records, completion_status, output_truncated}' \ "$work_dir/control.json" ``` The failing call takes about two minutes. For comparison, the verifier also sent tokenized requests directly to the same GDB: ```bash printf '%s\n' \ '1-interpreter-exec console "set auto-load off"' \ '2-interpreter-exec console "shell printf abc"' \ '3-gdb-exit' | gdb --nx --quiet --interpreter=mi3 > "$work_dir/direct-mi.txt" tail -4 "$work_dir/direct-mi.txt" ``` Direct GDB emits `abc2^done` on one line and exits successfully. No inferior is launched. ### Expected behavior Return completed Evidence with the shell output and a correlated completion result, as the newline-terminated control does. A completed three-byte command should not depend on the shell adding LF. The [provider guide](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/reverse-engineering-tools.md#L34) explicitly permits unrestricted shell commands and promises raw MI/stream Evidence; [gdb_console's contract](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/contracts/gdbToolContracts.ts#L41) makes the same unrestricted-command promise. ### Impact and scope P2: admitted commands without a final newline consume the full deadline and return unknown completion plus incomplete cleanup. The three-byte case and a 9,000,000-byte newline-free pipeline both failed; both newline-terminated controls succeeded. Ensuring shell output ends in LF is an observed workaround. Raw payload bytes were retained in these cases. Persistent MCP behavior is source-derived from the shared manager and was not separately reproduced. No security boundary crossing or target execution was involved. ### REA version `rea-agents` 6.7.0, source commit `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`. Submission recheck fetched main at `d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f`: no commits or content changes since the baseline in `docs/reverse-engineering-tools.md`, `src/contracts/gdbToolContracts.ts`, or `src/gdb/GdbSessionManager.ts`. Runtime reproduction was not rerun for submission. ### Runtime and platform Verified runtime: Node.js v24.22.0; Debian GDB 13.1-3; Linux amd64 container under emulation. REA and direct GDB ran in the same container. Submission host only: Node.js v26.12.0, macOS 27.0.1, arm64; no macOS GDB reproduction is claimed. ### Target and artifact context Caller-selected pseudo-terminal, isolated HOME/XDG/temp directories, and shell stdout. No executable fixture or inferior was started. The source checkout was mounted read-only during verification. ### Interface CLI ### Earliest failure stage Capture or observation ### Relevant output or evidence Independent verifier's observed results: | Input | Direct GDB | REA | | --- | --- | --- | | `shell printf abc` | exit 0, 0.171 s | exit 1, 121.571 s, `cleanup_incomplete` | | `shell printf 'abc\n'` | exit 0, 0.229 s | exit 0, 1.468 s, complete | The failing JSON reason starts `GDB command output could not be correlated and its owned process could not be confirmed stopped`; underlying execution failure is `capability_unavailable`. Its partial raw result is: ```json { "mi": "", "records": ["abc4^done", "(gdb) "], "console": "", "target": "", "log": "", "output_truncated": false, "completion_status": "unknown" } ``` The passing control has records `["abc", "4^done"]`, complete status, and no truncation. The verifier also observed the 9,000,000-byte newline-free pipeline fail after 121.506 s with all payload bytes followed immediately by `4^done`; piping the same payload through `awk '{print}'` succeeded in 1.734 s. These comparisons distinguish the missing boundary from generic large-output failure or emulation slowness. ### Typed outcome or recovery expected Completed Evidence preserving the three output bytes and correlated completion. Keep the newline control passing, genuine timeouts explicit, and existing protocol-resource limits intact. Proposed restoration check: repeat both small inputs and the below-budget 9,000,000-byte pair; no fix has been tested. ### Analysis or suspected cause Source-derived explanation: [GdbSessionManager.ts:1153](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/gdb/GdbSessionManager.ts#L1153) sends MI interpreter-exec console requests. The receiver [collects LF-delimited records](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/gdb/GdbSessionManager.ts#L1531-L1568), then [matches completion only at the start of a line](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/src/gdb/GdbSessionManager.ts#L1619). Real GDB emits unframed shell stdout immediately before its token, so `abc4^done` does not match. The resulting deadline leaves MI synchronization and inferior-state cleanup unknown. This mechanism agrees with the real-provider transcript and passing LF controls; it is not attributed to a bundled dependency. ### Additional context Prepared with AI assistance and independently reproduced by a separate verifier; the submitter inspected the saved small-case outputs, controls, source, and current-main diff. No earlier known-good version was established. A separate oversized MI-line guard is intentional and is outside this report. Related #4015 concerns Rizin losing pending bytes on exit/timeout; this GDB case retains bytes but misses an already emitted completion. The retained-summary feature #5584 and lifecycle PRs #1961/#2799 do not cover this trigger; #1961 retains the anchored completion matcher.
Author
Owner

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

Good repro — this is a completion-correlation issue, not a payload-loss one: GDB finishes the command (abc^done arrives), but the completion matcher anchored at line start can't see it because the shell output and the ^done record land on the same line when there's no trailing LF. The newline-terminated control proves the bytes are fine; only the correlation fails. Two directions worth considering: scan for the completion record even when it is not at line start (treat the trailing ^done as the boundary after the payload), or issue shell commands through tokenized MI like the direct-gdb control so ^done is always a standalone record. The latter looks closer to gdb_console's contract that promises unrestricted shell commands, and it would also keep the deadline math honest for newline-free pipelines.

<!-- gh-comment-id:6307794220 --> @XinwangTao commented on GitHub (Oct 21, 2026): Good repro — this is a completion-correlation issue, not a payload-loss one: GDB finishes the command (abc^done arrives), but the completion matcher anchored at line start can't see it because the shell output and the ^done record land on the same line when there's no trailing LF. The newline-terminated control proves the bytes are fine; only the correlation fails. Two directions worth considering: scan for the completion record even when it is not at line start (treat the trailing ^done as the boundary after the payload), or issue shell commands through tokenized MI like the direct-gdb control so ^done is always a standalone record. The latter looks closer to gdb_console's contract that promises unrestricted shell commands, and it would also keep the deadline math honest for newline-free pipelines.
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#3502
No description provided.