Skip to content

docs(runtime): document StreamStopped ordering and teardown trade-offs (#3074)#3275

Merged
Sayt-0 merged 1 commit into
mainfrom
docs/3074-streamstopped-ordering
Jun 26, 2026
Merged

docs(runtime): document StreamStopped ordering and teardown trade-offs (#3074)#3275
Sayt-0 merged 1 commit into
mainfrom
docs/3074-streamstopped-ordering

Conversation

@Sayt-0

@Sayt-0 Sayt-0 commented Jun 26, 2026

Copy link
Copy Markdown
Member

What

Follow-up to #3074, the documentation and decision debt left by #3070 (fix for #3069). No production behavior change: the only code edits are doc comments; the runtime ordering is kept as-is (Option B) and a regression test is added.

Decision: Option B (keep current order)

StreamStopped stays emitted before the session-end hooks and telemetry, rather than moving it after (Option A). An audit of every StreamStoppedEvent consumer (TUI chat, sidebar, supervisor, leantui, A2A adapter, API server) shows none rely on session-end hooks having completed, so the order is safe, and emit-before-hooks keeps the TUI responsive (spinner stop and queue drain are not delayed behind user-configured session_end hooks). The authoritative terminal signal remains the channel close, not the event. Full rationale and the consumer audit are in #3074.

This is opened as a draft because #3074 listed Option A as the recommended option; a maintainer decision on B versus A would unblock marking it ready. Option A is a one-line move and the docs plus test apply unchanged either way.

Changes

File Change
pkg/runtime/elicitation.go Doc on restoreAndClose: close-under-write-lock is the #3069 fix, and the accepted trade-off that teardown blocks (goroutine leak) instead of panicking when a parked sender cannot drain
pkg/runtime/loop.go Doc on finalizeEventChannel: ordering decision (#3074) and best-effort delivery; inline note on the non-blocking emit warning against reintroducing the #3070 deadlock
pkg/runtime/event.go Doc on StreamStoppedEvent: best-effort delivery, channel close is the terminal signal, hooks run after the event
pkg/runtime/elicitation_test.go New TestLocalRuntime_FinalizeEventChannelStreamStoppedIsLastBeforeClose: asserts exactly one StreamStopped and that it is the last event delivered before the channel close

Issue scope mapping

Issue item Status
Ordering decision Option B, documented
Document restoreAndClose leak-over-panic trade-off done
Document StreamStopped best-effort delivery done
Test: StreamStopped last before close done

Validation

Command Result
task build pass
golangci-lint run ./pkg/runtime/... 0 issues
go test ./pkg/runtime/ pass
go test -race ./pkg/runtime/ -run 'Elicitation|FinalizeEventChannel|RunStreamClosesChannel' pass

Note: go test -race ./pkg/runtime/ (full package) reports a data race in TestHandleStream_ContextCancellation. It is pre-existing and unrelated to this change (reproduced on a tree with these edits stashed) and lives in the stalledStream.Close() test double in streaming_test.go, which is invoked concurrently without synchronization. It does not surface under the standard task test run (no -race). Out of scope here; a separate fix can add a mutex or atomic to stalledStream.Close().

Relations

Capture the decisions left open by #3070: keep StreamStopped emitted before
session-end hooks (Option B), since no StreamStoppedEvent consumer relies on
hooks having finished and emit-before-hooks keeps the TUI responsive. Document
that the channel close, not the event, is the terminal signal and that the
event is best-effort. Document the accepted goroutine-leak-over-panic trade-off
in restoreAndClose. Add a regression test asserting StreamStopped is the last
event delivered before the channel close.

Refs #3074

@docker-agent docker-agent 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.

Assessment: 🟢 APPROVE

This is a documentation-only PR adding doc comments to three runtime files and one regression test. No production logic changes.

Drafter findings: The single medium-severity hypothesis (StreamStopped potentially dropped due to buffer exhaustion in the new test) was dismissed by verification: defaultEventChannelCapacity = 128 leaves 126 free slots after the 2 pre-seeded events, so the non-blocking emit succeeds without issue.

The remaining low-severity observations (doc understatement about best-effort delivery, latent test fragility around synchronous channel close) are pre-existing design characteristics, not problems introduced by this PR.

The new test correctly pins the Option B ordering decision from #3074: StreamStopped is emitted before session-end hooks, it is the last event before channel close, and the assertion structure is sound.

@Sayt-0
Sayt-0 marked this pull request as ready for review June 26, 2026 16:06
@Sayt-0
Sayt-0 requested a review from a team as a code owner June 26, 2026 16:06

@docker-agent docker-agent 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.

Assessment: 🟢 APPROVE

This PR is a documentation-only change — no production behaviour is altered. The new doc comments on restoreAndClose, StreamStoppedEvent, and finalizeEventChannel accurately describe the existing code, correctly document the #3069/#3070/#3074 design decisions, and follow the project's "explain the why" comment philosophy from AGENTS.md.

The new regression test (TestLocalRuntime_FinalizeEventChannelStreamStoppedIsLastBeforeClose) correctly exercises the ordering guarantee using a buffered channel (defaultEventChannelCapacity = 128) and a real runtime instance, with no race conditions identified.

No bugs introduced by the + lines were found.

@Sayt-0
Sayt-0 merged commit 5b2ba25 into main Jun 26, 2026
15 checks passed
@Sayt-0
Sayt-0 deleted the docs/3074-streamstopped-ordering branch June 26, 2026 16:19
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.

decide StreamStopped ordering vs session-end hooks and document teardown trade-offs from #3070

3 participants