[GH-ISSUE #6660] [Bug] GDB source commands finish at the first inferior stop and misattribute remaining output #4039

Closed
opened 2026-10-31 00:46:17 +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/6660

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

A single gdb_console command that sources a command file containing two sequential run commands returns completion_status: "complete" after the first inferior stops. Its console stream omits the file's trailing marker. The immediately following, unrelated console command receives the second run's output and that marker in both its result and Evidence. Neither reply is marked truncated.

This was executed against the real source GdbSessionManager and real GDB, the same owner called by the MCP adapter; a full MCP client round trip was not run.

Reproduction

Executed reduced procedure, with the scratch filename normalized. From the source checkout with its locked dependencies installed, use its pinned Node as node, save the following as /tmp/rea-gdb-source-repro.mts, then run:

REA_CHECKOUT="$PWD" node --import ./node_modules/tsx/dist/loader.mjs /tmp/rea-gdb-source-repro.mts
import { pathToFileURL } from 'node:url';
import { resolve, join } from 'node:path';
import { tmpdir } from 'node:os';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
const { GdbSessionManager } = await import(pathToFileURL(resolve(process.env.REA_CHECKOUT!, 'src/gdb/GdbSessionManager.ts')).href);
const directory = await mkdtemp(join(tmpdir(), 'rea-gdb-repro-'));
const commandFile = join(directory, 'commands.gdb');
await writeFile(commandFile, 'file /usr/bin/sleep\nset args 0.1\nrun\nrun\necho REA_SOURCE_TAIL\\n\n');
const manager = new GdbSessionManager({environment:{PATH:'/usr/bin:/bin',HOME:'/tmp',DEBUGINFOD_URLS:''}});
const started = await manager.start();
if (!started.ok) throw started.error;
const id = started.value.session_id;
try {
  const first = await manager.execute(id, `source ${commandFile}`);
  const second = await manager.execute(id, 'echo REA_NEXT_COMMAND\\n');
  for (const [label, result] of [['source',first],['next-command',second]] as const) {
    console.log(JSON.stringify({label,ok:result.ok,...(result.ok ? {mi:result.value.result.mi,completion:result.value.result.completion_status,truncated:result.value.result.output_truncated,console:result.value.result.console,evidence_console:result.value.evidence.normalized_result.console} : {error:result.error._tag})}));
  }
} finally {
  console.log(JSON.stringify({label:'close',result:await manager.close(id)}));
  await manager.closeAll();
  await rm(directory,{recursive:true,force:true});
}

The reduced two-run procedure showed the failure in its one executed run; an earlier two-run procedure also showed cross-command contamination. Only fresh, self-owned /usr/bin/sleep processes ran, and both exited normally before cleanup. The script removes its command file and closes its GDB session.

Expected behavior

The source observation should retain the command file's output through its actual completion, including REA_SOURCE_TAIL, and the next command's Evidence should contain its own output rather than the file's remaining output. Completion must not be reported while that command file is still executing. The console contract promises the selected CLI command's records with Evidence. GDB's command-file contract specifies sequential execution of the file's commands.

Impact and scope

Observed incorrect command completion and output provenance for an ordinary local command file. The second run's output is associated with the next command, and the source observation silently omits it. An attempted one-run reduction also returned complete without its trailing marker, but that marker was absent from both command captures in that run; output chunk timing affects whether later output is lost or attributed to a follower. This is a bounded non-security evidence-correlation defect. A direct GDB/MI control executed the earlier two-run fixture (0.2/0.6-second runs and different marker text) and emitted its final marker after the second stop. The reduced 0.1-second fixture was not separately run through direct GDB. Other GDB versions and command forms were not tested.

REA version

Source checkout at 23def04096869b85c4d3c14a73ed09168483c9f6 (package.json version 6.7.0; this main revision is newer than the published release). No packaged-release reproduction is claimed.

Runtime and platform

Node.js v24.22.1; Linux x86_64; GNU GDB 18.1. The installed GDB matched the latest stable release advertised by the official GDB project before the probe.

Target and artifact context

Two finite runs of the local /usr/bin/sleep ELF executable, each with argument 0.1; a temporary plain-text GDB command file; no pre-existing debugger sessions or external targets.

Interface

Other

Earliest failure stage

Capture or observation

Relevant output or evidence

Observed reduced output, omitting unrelated library and process diagnostics:

source: ok=true, mi="2^running", completion="complete", truncated=false
source console and Evidence console: no REA_SOURCE_TAIL
next-command: ok=true, mi="3^done", completion="complete", truncated=false
next-command console and Evidence console include:
~"REA_SOURCE_TAIL\n"
~"REA_NEXT_COMMAND\n"
close: {"ok":true,"value":{"target_state":"none_observed"}}

The reduced procedure reads both the manager result and evidence.normalized_result.console; both showed the same misplaced marker. The production MCP call path delivers that Evidence to the caller. No full-suite test or fixed-build verification was performed.

Typed outcome or recovery expected

Keep completion and command provenance truthful. Restoration check: the same source command captures the second run and final marker before it is complete, and the following echo command contains REA_NEXT_COMMAND without REA_SOURCE_TAIL or the source file's second-run output. Raw MI quoting should remain intact.

Analysis or suspected cause

Source-derived explanation: the MI receive path treats the first *stopped after ^running as command completion, deletes its pending collector, and snapshots its records. A source command can continue executing further commands after that stop. Evidence construction then preserves the prematurely bounded capture under the submitted command.

Additional context

Open and closed issue searches found no matching source-command completion/output-provenance report. Existing PR #1961 covers queued interruption while a run is pending; this report instead concerns a command file returning too early and transferring its remaining output to a different command. No claim is made about that PR's runtime behavior. Direct GDB was a verified workaround for the earlier two-run fixture, whose control transcript included the final marker after both runs. Direct GDB was not separately tested with the reduced 0.1-second fixture.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The executed local command file returned a complete, untruncated observation missing later output, and a different command's Evidence acquired that output. The observed scope is bounded command-file capture and provenance.
  • Workaround status: available
  • Workaround basis: The direct GDB/MI control completed the earlier two-run fixture, using 0.2/0.6-second runs and different marker text. It avoids REA's premature capture boundary for that observed fixture; direct GDB was not separately tested with the reduced 0.1-second fixture.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6660 ### 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 A single `gdb_console` command that sources a command file containing two sequential `run` commands returns `completion_status: "complete"` after the first inferior stops. Its console stream omits the file's trailing marker. The immediately following, unrelated console command receives the second run's output and that marker in both its result and Evidence. Neither reply is marked truncated. This was executed against the real source `GdbSessionManager` and real GDB, the same owner called by the MCP adapter; a full MCP client round trip was not run. ### Reproduction Executed reduced procedure, with the scratch filename normalized. From the source checkout with its locked dependencies installed, use its pinned Node as `node`, save the following as `/tmp/rea-gdb-source-repro.mts`, then run: ```sh REA_CHECKOUT="$PWD" node --import ./node_modules/tsx/dist/loader.mjs /tmp/rea-gdb-source-repro.mts ``` ```ts import { pathToFileURL } from 'node:url'; import { resolve, join } from 'node:path'; import { tmpdir } from 'node:os'; import { mkdtemp, writeFile, rm } from 'node:fs/promises'; const { GdbSessionManager } = await import(pathToFileURL(resolve(process.env.REA_CHECKOUT!, 'src/gdb/GdbSessionManager.ts')).href); const directory = await mkdtemp(join(tmpdir(), 'rea-gdb-repro-')); const commandFile = join(directory, 'commands.gdb'); await writeFile(commandFile, 'file /usr/bin/sleep\nset args 0.1\nrun\nrun\necho REA_SOURCE_TAIL\\n\n'); const manager = new GdbSessionManager({environment:{PATH:'/usr/bin:/bin',HOME:'/tmp',DEBUGINFOD_URLS:''}}); const started = await manager.start(); if (!started.ok) throw started.error; const id = started.value.session_id; try { const first = await manager.execute(id, `source ${commandFile}`); const second = await manager.execute(id, 'echo REA_NEXT_COMMAND\\n'); for (const [label, result] of [['source',first],['next-command',second]] as const) { console.log(JSON.stringify({label,ok:result.ok,...(result.ok ? {mi:result.value.result.mi,completion:result.value.result.completion_status,truncated:result.value.result.output_truncated,console:result.value.result.console,evidence_console:result.value.evidence.normalized_result.console} : {error:result.error._tag})})); } } finally { console.log(JSON.stringify({label:'close',result:await manager.close(id)})); await manager.closeAll(); await rm(directory,{recursive:true,force:true}); } ``` The reduced two-run procedure showed the failure in its one executed run; an earlier two-run procedure also showed cross-command contamination. Only fresh, self-owned `/usr/bin/sleep` processes ran, and both exited normally before cleanup. The script removes its command file and closes its GDB session. ### Expected behavior The `source` observation should retain the command file's output through its actual completion, including `REA_SOURCE_TAIL`, and the next command's Evidence should contain its own output rather than the file's remaining output. Completion must not be reported while that command file is still executing. The [console contract](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/contracts/gdbToolContracts.ts#L37-L49) promises the selected CLI command's records with Evidence. GDB's [command-file contract](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Command-Files.html) specifies sequential execution of the file's commands. ### Impact and scope Observed incorrect command completion and output provenance for an ordinary local command file. The second run's output is associated with the next command, and the source observation silently omits it. An attempted one-run reduction also returned complete without its trailing marker, but that marker was absent from both command captures in that run; output chunk timing affects whether later output is lost or attributed to a follower. This is a bounded non-security evidence-correlation defect. A direct GDB/MI control executed the earlier two-run fixture (0.2/0.6-second runs and different marker text) and emitted its final marker after the second stop. The reduced 0.1-second fixture was not separately run through direct GDB. Other GDB versions and command forms were not tested. ### REA version Source checkout at `23def04096869b85c4d3c14a73ed09168483c9f6` (`package.json` version `6.7.0`; this main revision is newer than the published release). No packaged-release reproduction is claimed. ### Runtime and platform Node.js `v24.22.1`; Linux x86_64; GNU GDB `18.1`. The installed GDB matched the latest stable release advertised by the [official GDB project](https://www.sourceware.org/gdb/) before the probe. ### Target and artifact context Two finite runs of the local `/usr/bin/sleep` ELF executable, each with argument `0.1`; a temporary plain-text GDB command file; no pre-existing debugger sessions or external targets. ### Interface Other ### Earliest failure stage Capture or observation ### Relevant output or evidence Observed reduced output, omitting unrelated library and process diagnostics: ```text source: ok=true, mi="2^running", completion="complete", truncated=false source console and Evidence console: no REA_SOURCE_TAIL next-command: ok=true, mi="3^done", completion="complete", truncated=false next-command console and Evidence console include: ~"REA_SOURCE_TAIL\n" ~"REA_NEXT_COMMAND\n" close: {"ok":true,"value":{"target_state":"none_observed"}} ``` The reduced procedure reads both the manager result and `evidence.normalized_result.console`; both showed the same misplaced marker. The production [MCP call path](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/server/registerGdbTools.ts#L87-L115) delivers that Evidence to the caller. No full-suite test or fixed-build verification was performed. ### Typed outcome or recovery expected Keep completion and command provenance truthful. Restoration check: the same source command captures the second run and final marker before it is complete, and the following echo command contains `REA_NEXT_COMMAND` without `REA_SOURCE_TAIL` or the source file's second-run output. Raw MI quoting should remain intact. ### Analysis or suspected cause Source-derived explanation: the [MI receive path](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/gdb/GdbSessionManager.ts#L1638-L1668) treats the first `*stopped` after `^running` as command completion, deletes its pending collector, and snapshots its records. A `source` command can continue executing further commands after that stop. [Evidence construction](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/gdb/GdbSessionManager.ts#L1191-L1222) then preserves the prematurely bounded capture under the submitted command. ### Additional context Open and closed issue searches found no matching source-command completion/output-provenance report. Existing PR [#1961](https://github.com/morluto/rea/pull/1961) covers queued interruption while a run is pending; this report instead concerns a command file returning too early and transferring its remaining output to a different command. No claim is made about that PR's runtime behavior. Direct GDB was a verified workaround for the earlier two-run fixture, whose control transcript included the final marker after both runs. Direct GDB was not separately tested with the reduced 0.1-second fixture. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The executed local command file returned a complete, untruncated observation missing later output, and a different command's Evidence acquired that output. The observed scope is bounded command-file capture and provenance. - Workaround status: available - Workaround basis: The direct GDB/MI control completed the earlier two-run fixture, using 0.2/0.6-second runs and different marker text. It avoids REA's premature capture boundary for that observed fixture; direct GDB was not separately tested with the reduced 0.1-second fixture.
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#4039
No description provided.