Skip to content

fix(proxy): forward a program's last line without a newline before its exit on Windows (#860) - #869

Merged
debugmcpdev merged 2 commits into
mainfrom
fix/860-flush-trailing-fragment
Oct 7, 2026
Merged

debugmcpdev merged 2 commits into
mainfrom
fix/860-flush-trailing-fragment

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

What

On Windows a Rust/C++/COBOL program's last output was lost when it did not end in a newline (#860). The debuggee writes into CodeLLDB's stdio pipes there, and GenericAdapterManager.consumeStream splits that stream with a LineBuffer, flushing the trailing fragment only on the stream's end/close. CodeLLDB keeps its pipes open until teardown, so the fragment was flushed after exited/terminated had been forwarded and the session had stopped listening.

How

  • AdapterSpawnResult.flushStdio?: () => void (optional, so every { spawn, shutdown } stub returning { process, pid } keeps type-checking): the early flush of both stdio line buffers, delivering what they hold through onStdioLine marked { partial: true }. Complete lines keep the two-argument call; a later end/close finds an emptied buffer.
  • The worker keeps it for an adapter that outlives its debuggee (forwardStdio.adapterOutlivesDebuggee, i.e. the rust/cpp/cobol win32 spawn config) and runs it inside waitForAdapterStdioDrain once the race settles — on the measured quiet window or the backstop (Windows: the end of a Rust/C++/COBOL program is reported 2 s late — the stdio drain waits for a pipe close CodeLLDB never produces #856). That covers all four terminal callers (adapter exit, exited, terminated, dap_connection_closed) and lands before each forwards its signal, so the fragment is ahead of exited on the FIFO IPC channel. When the pipes did close, the manager's own close flush ran first and this finds nothing.
  • buildStdioForwarder leaves a partial line without the '\n' it restores for complete lines: the fragment arrives as printed.
  • consumeStream's comment keeps the Proxy stderr sanitization is per-chunk, not per-line: a secret split across a stream chunk can leak its tail #151 straddle reasoning (never flush on process exit while the pipe is live) and explains why the silence-driven flush is different.

rdbg (pipes close with the program) is unchanged — covered by a test.

Measured (Windows 11, cpp adapter via the dev server, MSYS2 g++ 15.2.0)

The issue's program (printf("first line\n"); printf("result: 42");):

get_output after the launch answered stopped (exit 0)
main @ 55fbfea first line\n — nothing else
this PR first line\n, then result: 42 (no newline added), 103 ms later

A second program with a 2000-char stdout fragment and a stderr fragment (err-tail), exit code 3: both fragments arrive intact, exitCode: 3.

Tests

Fixes #860

🤖 Generated with Claude Code

…s exit on Windows (#860)

On Windows the debuggee of a CodeLLDB-based adapter (rust, cpp, cobol)
writes into the adapter's stdio pipes, and GenericAdapterManager splits
that stream into lines with a LineBuffer: complete lines are forwarded as
they arrive, the trailing fragment on the stream's end/close. CodeLLDB
keeps its pipes open until teardown, so a last line printed without a
newline was flushed after exited/terminated had been forwarded and the
session had stopped listening — `result: 42` never reached get_output.

AdapterSpawnResult gains an optional flushStdio(): the early flush of both
stdio line buffers, delivering what they hold through onStdioLine marked
`{ partial: true }`. The worker keeps it for an adapter that outlives its
debuggee and runs it when waitForAdapterStdioDrain settles — on the
measured quiet window or the backstop (#856) — before every caller
forwards its terminal signal, so the fragment lands ahead of exited on
the FIFO IPC channel. The forwarder leaves a partial line without the
'\n' it restores for complete lines: the fragment arrives as printed. When
the pipes did close, the manager's own close flush ran first and the early
flush finds nothing; rdbg (pipes close with the program) is unchanged.

Measured on Windows 11 through the dev server, cpp adapter: before,
get_output ended at `first line\n`; after, `result: 42` follows 103 ms
later (the quiet window), with no added newline, and a 2000-char stdout
fragment plus a stderr fragment arrive intact with exit code 3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: a Rust/C++/COBOL program's last output is lost when it does not end in a newline

2 participants