fix(harness): close a stream_event's open block before finish-step on error - #6675
Merged
Conversation
… error fail() emitted finish-step without closing whatever stream_event had left open, unlike the assistant-message step boundary a few lines above, which closes it first (added earlier in #6670 for the same orphan-end hazard). An SDK throw mid-block leaves that text/reasoning part without its -end chunk, so it reads as still streaming forever after the run has already failed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows #6670.
#6670 added token-level streaming and, in a same-PR follow-up fix, closes a
stream_event-opened text/reasoning block before thefinish-stepboundary between assistant messages — because the AI SDK's reducer clears its open parts onfinish-step, so an-endemitted after it targets a part the reducer no longer knows and throws, killing the run mid-stream.That same fix was applied at the multi-step boundary (
claude-code.ts, inside themessage.type === "assistant"branch) but missed the other place a turn ends:fail(), called when the SDK throws mid-turn (a network drop, a crash).fail()emittedfinish-stepdirectly, without first callingtranslator.closeOpenStreamBlocks(). If the SDK throws while astream_eventblock is still open (nocontent_block_stoparrived), that part never gets its-endchunk — no crash this time, but the message reads as still streaming forever in the UI even though the run has already failed and reported an error.Fix: extracted the step-boundary's "close what's open, then finish" pattern into a small exported helper (
errorFinishChunks) and used it infail()too.Regression test:
packages/harness-runner/claude-code.test.ts—errorFinishChunkswith a block left open bystream_event(nocontent_block_stop) now closes it (text-end) beforefinish-step/finish; confirmed this fails on the pre-fix code (the export doesn't exist there) and passes after.Reviewer check:
cd packages/harness-runner && bun test claude-code.test.tsLocally ran:
bun run fmt,bunx tsc --noEmit(packages/harness-runner),bun test packages/harness-runner/claude-code.test.ts(40 pass),bunx oxlinton both changed files (0 warnings/errors). Full CI validates the rest.Summary by cubic
Fixes the harness error path so a message no longer reads as still streaming after a mid-turn crash. Previously,
fail()emittedfinish-stepwithout closing anystream_event-opened block; now it reuses the same close-then-finish logic as the assistant-message step boundary.errorFinishChunkshelper and uses it infail().content_block_stop.Written for commit 1adca81. Summary will update on new commits.