Drop the Action Cable workarounds the client now handles - #407
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The lifecycle migration matches the updated dependency contracts and includes focused regression coverage.
Pull request overview
Adopts Action Cable’s lifecycle APIs and removes local connection-state workarounds.
Changes:
- Uses
Client.Err,Subscription.Err, and context-freeUnsubscribe. - Bounds initial cable connections and preserves underlying errors.
- Adds regression coverage for rejected subscriptions on live shared clients.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
File summaries
| File | Description |
|---|---|
internal/cmd/watch.go |
Reports subscription termination through Subscription.Err. |
internal/cmd/watch_test.go |
Tests interruption, closure, and rejection outcomes. |
internal/cmd/watch_calendar.go |
Updates subscription teardown API usage. |
internal/cmd/tui_watch.go |
Uses native client lifecycle state and teardown. |
internal/cmd/tui_watch_test.go |
Covers rejection without discarding a live client. |
internal/cable/cable.go |
Bounds connection opening and relies on client cleanup. |
internal/cable/cable_test.go |
Verifies credential and failed-dial behavior. |
go.mod / go.sum |
Updates actioncable-go to the required revision. |
Review details
- Files reviewed: 8/9 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The client reports its own lifecycle since basecamp/actioncable-go#6 and #7, so the code here that worked that out for itself can go. - A Connect that gives up stops the client, so Dial no longer closes one behind its own error. Dial bounds the opening itself rather than leaving a caller who passed no deadline inside the retry loop. - A dial's credentials are no longer fetched twice. The error from building the upgrade request comes back with the one Connect returns, so a token that can't be built is reported rather than retried out of sight. - Client.Err says whether the shared TUI connection stopped and why, in place of calling Connect again and reading ErrAlreadyConnected as a sign of life. It also stops a rejected subscription from condemning a connection the other watches are still using. - Subscription.Err says why the messages dried up, in place of the flag OnRejected used to set, and the watch now reports what it was. - Unsubscribe takes no context, so the goodbye no longer needs one built to outlive the watch that just ended. The Screener's separate reconnect channel stays. Its relay closes on the watch's context while the subscription behind it is still registered, so a late callback would still write to a closed channel — that never had anything to do with the dispatcher draining after a stop, and the comment said otherwise.
monorkin
force-pushed
the
worktree-actioncable-lifecycle
branch
from
September 8, 2026 05:00
6bc1402 to
9ee9bae
Compare
The lifecycle API this branch depends on has a release now, so the pseudo-version pointing at a commit can go.
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.
Picks up the Action Cable client's new lifecycle API (basecamp/actioncable-go#6 and #7) and drops the code here that was working around its silence.
Dialno longer closes a client behind its own error. AConnectthat gives up now stops the client itself.Dialbounds the opening with its own timeout rather than leaving a caller who passed no deadline inside the retry loop for good.authHeadercall is gone: an error from building the upgrade request now comes back wrapped in the oneConnectreturns, so a token that can't be built is reported instead of retried out of sight.Client.Errsays whether the shared TUI connection stopped, and why. That replaces callingConnecta second time and readingErrAlreadyConnectedas a sign of life. It also fixes a real case the probe got wrong: a rejected subscription would report a non-ErrAlreadyConnectederror and condemn a connection the other watches were still using.Subscription.Errsays why the messages dried up, in place of theatomic.BoolthatOnRejectedused to set, andhey watchnow reports what actually ended it.Unsubscribetakes no context, so the goodbye no longer needs one built to outlive the watch that just ended.One thing that stays
The Screener's separate reconnect channel. Its relay closes the channel when the watch's context ends, while the subscription behind it stays registered until the deferred goodbye, so a late callback would still write to a closed channel and panic off a goroutine Bubble Tea can't recover. That was never about the dispatcher draining callbacks after a stop, which is what its comment claimed — the comment is corrected here.
Checks
make checkandmake race-testboth pass. The new test for the live-client path was mutation-checked: it fails whensubscribeTuiCabletreats a rejection as terminal.