Skip to content

fix: release-review fixes before 0.12 (remote diagnostics, live-analysis tokens, publish safety, migration notes) - #171

Merged
carldebilly merged 17 commits into
mainfrom
dev/cdb/release-0-12-prep
Nov 3, 2026
Merged

carldebilly merged 17 commits into
mainfrom
dev/cdb/release-0-12-prep

Conversation

@carldebilly

@carldebilly carldebilly commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Fixes from the 0.12 release review panel that should land before nbgv prepare-release. The items deferred to 0.13 are tracked in #166, #167, #168, #169 and #170.

Changes

Not changed

  • WebSocketStream.WriteAsync keeps discarding writes after close. Throwing would break callers shipped with 0.11, so the documentation was corrected instead.
  • The suspected missing restore of the startup input in nested runs was refuted. A regression test now pins that a nested run does not replay the outer prepared input.

Test plan

  • Red before each fix, with a successful build: the remote callback tests, the live-analysis rows, the sample 10 client test.
  • Mutation checks: the new tests fail when the connection-failure report is removed, when the WriteAsync guard is narrowed to Aborted, and when the nested-run clear is removed.
  • Not covered by a test: the accept-loop fault, which has no seam to inject a fault. Noted in remote: robustness and cleanup follow-ups from the 0.12 release review #167.
  • The new launcher signal test sends a real SIGINT and SIGTERM. It runs on Unix only, like the existing process-signal tests, so it was not observed locally. CI runs it on Ubuntu and macOS.
  • Release build: 0 warnings.
  • Full suite: 2752 tests, 0 failed, 35 skipped. The skipped tests are the opt-in container and MCP Inspector tests.
  • markdownlint clean on the edited docs.

.editorconfig sets charset = utf-8, and no .cs file carried a byte-order
mark at v0.11.0. Thirty files picked one up during 0.12 work. Only the
three leading bytes are removed; line endings are untouched.
A failed .nupkg push was only a warning, and the job failed only when
every package failed. With the GitHub release already created, a green
run could hide a version whose packages are partly missing from NuGet;
0.12 adds two new packages, which widens that window. The job now fails
with an error naming the packages, and a re-run recovers through
--skip-duplicate as docs/publishing.md describes. A symbol package that
fails stays a warning, since it only degrades debugging.
A backend method failure reached the client as "The remote backend
failed." and its detail went only to System.Diagnostics.Trace, which no
default .NET logging provider reads. Terminal failures and a fault that
stopped the listener accepting were just as invisible, so an operator in
a container saw nothing while the client got a sanitized error.

NamedPipeReplServer now hands each of these exceptions to the
onConnectionError callback it already took, and RemoteReplServer.RunAsync
gains overloads taking the same callback as onError for hosts that serve
their own streams. The trace stays as the record when no callback is
given. A callback that throws is contained: the client still receives
its error and the connection keeps serving.

The accept loop also reports its fault before faulting Completion, and
sample 05 now watches Completion like sample 10, so the host stops
instead of running on without its administration endpoint.
docs/remote-cli.md documents the callback and updates its hosting example.

TDD: the backend and terminal tests were red (callback never called,
20 s timeout) with a successful build before the fix.
Live analysis reported a missing or unconvertible value with the C#
parameter name ("Supply a value for 'retryCount'."), while the binder's
refusals for the same parameter name the token the user types
("Option '--retryCount' requires exactly one value."). Both messages now
use the option's display token, falling back to the parameter name for an
argument-only parameter or a route value, through a new
OptionSchema.ResolveDisplayName.

TDD: both rows of the new test were red with a successful build before
the fix, and red again with the Core change stashed.
WriteAsync's remark said a failed connection is reported as an
IOException, but once the WebSocket is closing or closed (CloseSent,
Closed, Aborted) a write completes without sending and without throwing.
That has been the behaviour since 0.11, when the guard was an Open-only
check, so changing it now would break shipped callers. The remark now
says what happens: a failed send throws IOException, a write after the
close is discarded, writes after only the peer's close are still sent,
and a dead peer is detected by reading. A test pins the discard.
The release review asked whether the startup input set by a prepared run
could leak into a run started from one of its handlers, since
RunStartupOutcomeAsync restores _preparedStartup by hand but never
_startupInput. It cannot: PendingInvocationScope clears the startup input
for every new run and restores it on disposal. This test pins that.
Removing the clear makes it fail with the outer input replayed
(outer, outer, outer).
Sample 10's exit-code resolver wrote every failure's full exception to
stderr. That is what a failed serve needs in a container log, but a client
command against a server that is not running (health --ready, exec,
console) printed the framework's "Could not connect to pipe ..." line and
then a TimeoutException stack trace for the same condition. The resolver
now writes the exception only for the serve route.

TDD: the new client test was red, with the stack trace after the error
line, before the change; the existing serve test still requires the
full exception.
Run* rejects an undefined SessionScope, and a standalone run an
undefined ProcessSignalHandling, with ArgumentOutOfRangeException, since
numeric configuration or deserialization can produce such values. Neither
the options nor the overloads said so. The two properties now state when
the run fails, and each overload that takes ReplRunOptions documents the
exception.
The release review found breaking or observable changes whose migration
had no "Changed in 0.12" note where readers look for one:

- best-practices: Scoped resolves per session and per MCP connection (#74)
- configuration-reference and execution-pipeline: framework exit codes
  and stderr routing (#85); process-signal ownership, labelled (#80)
- mcp-reference: the operator events and the 2008 client message (#106,
  #163); the SDK 2.x upgrade section, labelled (#71)
- mcp-conformance: unknown resource and prompt error codes (#107)
- testing-toolkit: result read-through on Results.Exit (#128)
…after an accept fault

Review of the previous commit found three gaps:

- NamedPipeReplServer.DisposeAsync rethrew a fault that had already
  stopped the endpoint accepting, because draining awaited Completion.
  A host reacting to that fault, as samples 05 and 10 do, then logged a
  critical shutdown failure and called Environment.Exit(1), skipping its
  own disposal. Disposal now waits for the accept loop without rethrowing
  its fault, which Completion and the callback already carry.
- The listener's callback was named onConnectionError, although it now
  receives backend, terminal and endpoint failures, while RunAsync calls
  the same callback onError. Repl.Remote has not shipped, so the listener
  parameter is renamed onError, and sample 05 logs "Remote CLI failure".
- A cleanup failure after DisposeAsync timed out still went only to the
  trace; it now reaches the callback too.

The callback docs now say it may run for several connections at once.
A new test covers a backend factory that throws: the callback receives
the exception and the next connection is served. Removing the report
makes it fail. The accept-fault path has no seam to inject a fault and
remains untested.
GitHub ends a pwsh step with the last native command's exit code, so a
.snupkg push that failed last made the step fail although the script
treats symbol failures as warnings. The script now exits 0 after its own
package check, and records package and symbol failures in separate lists
instead of splitting one list by extension.
The ArgumentOutOfRangeException documentation said any undefined enum
setting of options throws, but only SessionScope, and ProcessSignalHandling
for a standalone run, are validated. It now names both. Two overloads
without an options parameter inherited that exception: the WebSocket
session's options-less RunAsync now inherits only the cancellation
exception, and ReplApp.RunAsync(request, cancellationToken) has its own
documentation.
- Live analysis: rows for a renamed option, expecting '--retries', and
  for an argument-only parameter, expecting its bare name, so neither
  "--" + parameter name nor a broken fallback passes.
- WebSocketStream: the silent discard is pinned for CloseSent and Closed
  as well as Aborted; narrowing the guard to Aborted fails both.
- Nested prepared run: the description no longer claims the test proves
  the outer input is restored, which the AsyncLocal guarantees anyway.
@carldebilly carldebilly added this to the 0.12.0 milestone Nov 3, 2026
@carldebilly

Copy link
Copy Markdown
Member Author

@autocarl please review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T06:05:50.878661Z dd40730 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Sample 10 opted out of Repl's signal handling with
ProcessSignalHandling = None, then handled Ctrl+C itself through
Console.CancelKeyPress and mapped the resulting cancellation to 130 by
hand. That covered less than the CLI profile it already used: SIGTERM,
which docker stop sends, and Ctrl+Break never cancelled a running client
command. The launcher now runs with the profile's default, so Repl
cancels the command cooperatively on any of them and an interrupted run
exits 130 or 143. The console mode is unaffected, since it reads Ctrl+C
as input and forwards it to the server.

A new test sends a real SIGINT or SIGTERM to the launcher while exec
waits on a server the test owns, and expects the remote command to be
cancelled and the conventional exit code. Like the existing process
signal tests it runs on Unix only, so it was not observed locally,
where it is skipped.

@autocarl autocarl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 4afb343, including the new launcher signal commit. One confirmed P2 in publication failure accounting; see inline finding.

Completed validation: Release solution build 0 warnings / 0 errors; seven test suites 2,718 passed / 25 skipped / 0 failed, including real SIGINT/SIGTERM launcher tests. Additional accept-loop fault injection verifies the operator callback, faulted Completion, clean disposal and backend ownership, including a throwing callback. Baseline controls reproduce four option-token diagnostic failures and the stopped-server stack trace; argument-only controls remain green.

The PowerShell branch simulation passed, but its fake dotnet executable did not model automatic symbol upload. Independent review caught that coverage gap, and a real CLI/local HTTP reproduction confirms the inline issue. No external packages were published.

Independent static reviews covered the unchanged remote, input, docs and publishing changes at 3ddade7; parent inspected the new delta and reran the full build/suites at this head. Applicable CI is green. No further introduced P0-P2 confirmed. Requesting the small publishing correction before approval; no merge or product edits performed.

Comment thread .github/workflows/ci.yml Resolved
dotnet nuget push on a .nupkg also pushes the adjacent .snupkg unless
--no-symbols is given. A failure of the symbol endpoint alone made that
command exit non-zero after the package itself was published, so the
package was counted as failed and the job failed, against the policy that
a symbol failure is only a warning. The package loop now passes
--no-symbols; the symbol loop pushes the .snupkg files on their own.
The cancellation table only said that stopping the server or container
stops the host. It now names Ctrl+C, Ctrl+Break and SIGTERM, including
docker stop, which Repl now owns in the launcher, and their exit codes.
@carldebilly

Copy link
Copy Markdown
Member Author

@autocarl please review

@autocarl autocarl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Targeted revalidation at 12fc35d: the previously requested publishing correction is verified fixed (see existing thread). No remaining technical blocker confirmed. Only workflow and architecture documentation changed since fully validated 4afb343; no C# rebuild or full-suite rerun was warranted. The new workflow was exercised with the real CLI/local HTTP endpoint for success, symbol-only failure and package failure; results are 0, 0 and 1 respectively. Previous C# validation was 2,718 passed / 25 skipped. Formal approval is held only for the current head CI jobs still in progress, not for another code correction. No merge or external package publication performed.

@autocarl autocarl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at exact head 12fc35d. This supersedes my earlier REQUEST_CHANGES review: the automatic-symbol-upload P2 is fixed and verified, and the only remaining gate (current-head CI) is now successful, including Linux/Windows/macOS, documentation runtimes/container, Inspector, packaging and stress checks. No new code changes since the targeted revalidation, so no additional rebuild was performed. Prior unchanged-C# validation: Release solution build clean, 2,718 passed / 25 skipped. Updated publication script tested with the actual CLI against a local HTTP endpoint: full success exits 0, symbol-only failure exits 0, package failure exits 1. No remaining confirmed introduced P0-P2. No merge performed.

Follow-up to an ai-slop review of this branch:

- The ArgumentOutOfRangeException sentence was pasted on 22 Run*
  overloads, and a reword already had to touch every copy. It now lives
  on ReplApp.RunAsync(string[], ReplRunOptions?, CancellationToken); the
  other overloads inherit that exception with the repository's existing
  inheritdoc path idiom.
- The single-stream RemoteReplServer.RunAsync documents onError by
  pointing at the two-stream overload instead of repeating its text.
- Sample 10's Program.cs comment and architecture overview point at the
  cancellation table, which alone states what each signal stops and the
  exit codes.
@carldebilly
carldebilly merged commit 5463aaa into main Nov 3, 2026
16 of 17 checks passed
@carldebilly
carldebilly deleted the dev/cdb/release-0-12-prep branch November 3, 2026 06:06
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.

2 participants