镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Treat a read whose handler threw OperationCanceledException as failed [patch] - #146

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/handler-cancellation-is-a-failed-read-144
Nov 10, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/handler-cancellation-is-a-failed-read-144

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #144

What was wrong

ReadChunks is async. When an OutputHandler or LineOutputHandler delegate throws OperationCanceledException or TaskCanceledException, the read task ends Canceled, not Faulted. DrainOrAbandon only checked IsFaulted, so it treated that read as a clean finish:

What changed

DrainOrAbandon now treats IsFaulted || IsCanceled as a failed read. It abandons the other read and awaits the failed one, so the handler's own exception reaches the caller and RunAsync's existing catch kills the tree. Cancellation from the caller's token still goes through its own cancelled branch and is unaffected. I used IsCanceled rather than IsCompletedSuccessfully because the latter doesn't exist on the netstandard2.0 target. CLAUDE.md's reader section now notes the case.

Tests

  • ExecuteAsyncShouldThrowAndKillTheCommandWhenTheOutputHandlerThrowsOperationCanceled and ...WhenALineOutputHandlerThrowsTaskCanceled reuse AssertAFaultedReadEndsTheCall, writing about 1 MB after the throwing chunk. They require the call to end within 10 s, finish Canceled with the handler's own exception (same message), and leave the command's pid gone.
  • ExecuteAsyncShouldRethrowAnOperationCanceledExceptionFromTheOutputHandlerWhenTheOutputIsSmall covers the small-output case: the exception is thrown instead of the exit code being returned, and delivery stops at the chunk that threw.

Before the fix, all three new tests failed: the two large-output cases hit the 10 s bound with the child still blocked, and the small-output case got "no exception was thrown". After the fix the full suite passes on net10.0 / Linux: 93 passed, 0 failed, and the 2 elevation tests skipped as usual.


Generated by Claude Code

… [patch]

A handler that throws OperationCanceledException or TaskCanceledException ends its async
read Canceled, not Faulted. DrainOrAbandon checked only IsFaulted, so it took that read for a
clean finish: with small output ExecuteAsync returned the exit code as if the run had
succeeded, and with more output than the pipe holds it hung on a command blocked writing to
the pipe nobody drained any more (the #89 hang through another exception type).

A Canceled read now abandons the other read and is awaited like a Faulted one, so the
handler's own exception reaches the caller and RunAsync kills the tree. Cancellation from the
caller's token still has its own branch and never arrives through a read.

Fixes #144

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW45a4XVwC8wG7P95kY
@sonarqubecloud

sonarqubecloud Bot commented Nov 8, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 0aa99fa into main Nov 10, 2026
15 checks passed
@matt-edmondson
matt-edmondson deleted the fix/handler-cancellation-is-a-failed-read-144 branch November 10, 2026 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants