Skip to content

Highest/fix reconnect#214

Merged
highesttt merged 3 commits into
mainfrom
highest/fix-reconnect
Jul 10, 2026
Merged

Highest/fix reconnect#214
highesttt merged 3 commits into
mainfrom
highest/fix-reconnect

Conversation

@highesttt

Copy link
Copy Markdown
Collaborator

No description provided.

@indent-zero

indent-zero Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Fixes two reconnect/state-tracking bugs in the LINE bridge: the framework was auto-spawning a management-room "LINE bot" chat when reporting disconnected states, and the StateConnected update sent from finishLogin before Connect completed was masking bad-credentials errors so the framework never auto-recovered. The client lifecycle is rewritten around a superseded flag + cancellable "runs" + recoverMu-serialized transitions, and SSE polling gains an independent 150s Talk-auth probe that survives heartbeats/EOF reconnects.

  • LineConnector.Init now forces bridge.Config.BridgeStatusNotices = "none" so framework status notices can't create the LINE bot management room.
  • finishLogin no longer sends StateConnected itself; the new sendConnectedStateIfCurrent inside Connect emits it only after startup actually succeeds, so StateBadCredentials is surfaced correctly for reconnect logic.
  • New beginRun/retire/Disconnect machinery cancels in-flight startup + SSE work when the client is superseded and drains pending recovery/logout under recoverMu before returning.
  • markLoggedOutByOtherClient now runs under recoverMu, short-circuits on superseded.Load(), and uses claimForcedLogoutState to emit the bad-credentials notice at most once per client.
  • StartWithOverride remembers the reused UserLogin so finishLogin can retire() the old LineClient before NewLogin swaps in the replacement.
  • pollLoop replaces the SSE-idle-triggered auth probe with a scheduled 150s probe context (WithDeadlineCause(errReceiveAuthProbeDue)); handleReceiveAuthProbe (renamed from handleReceiveIdleTimeout) runs on every probe deadline regardless of stream activity and preserves localRev.
  • ensureValidToken/recoverToken are refactored into *With seams that thread context.Context cancellation through refresh/re-login and treat isLoggedOut/errLineSessionInvalidated from refresh as terminal; new errLineClientSuperseded sentinel + pre/post checks in runTokenRecovery.
  • pkg/line HTTP layer gains context-aware callRPCContext / GetProfileContext / GetLastOpRevisionContext variants using http.NewRequestWithContext; getProfileWithToken and getLastOpRevisionWithClient signatures updated accordingly.
  • Extensive new test coverage: client lifecycle (superseded/retire/cancellation), forced-logout vs. in-flight recovery race, receive-auth probe scheduling + EOF handling, RPC context cancellation, and SSE context cancellation with continuous heartbeats.

Issues

1 potential issue found:

  • LineConnector.Init unconditionally overwrites bridge.Config.BridgeStatusNotices = "none", so any user-supplied value (errors / all) is silently discarded — the connector_test.TestInitDisablesManagementRoomBridgeStatusNotices test even asserts this override for all three configured values. The comment ties it to preventing the management-room bot chat from being spawned on disconnect, but this removes admin/error visibility for anyone who was relying on it and offers no config knob to opt back in. → Autofix
1 issue already resolved
  • Stale-client protection in markLoggedOutByOtherClient regressed for fresh-login (non-override) flow: the removed lc.UserLogin.Client != lc guard was replaced with lc.superseded.Load(), which is only set via Disconnect/retire. StartWithOverride calls oldClient.retire(), but the Start+SubmitUserInput fresh-login path doesn't, and bridgev2.User.NewLogin reuses an existing UserLogin by ID without disconnecting the previous Client (mautrix/go v0.28.0 userlogin.go:185-247). Trigger: user runs a fresh login for an account whose mid already has a UserLogin — the old client's SSE auth probe will still fire markLoggedOutByOtherClient, pass the superseded check, and overwrite the newly-authenticated UserLogin.Metadata with AccessToken="" + SessionInvalidated=true, silently invalidating the fresh session. (fixed by commit e70fd3c)

CI Checks

All checks passing on e70fd3c. The prior staticcheck SA2001 failure was resolved by adding lc.recoveryStopped = true inside the previously-empty recoverMu barrier in Disconnect.


⚡ Autofix All Issues

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab66bc52-c7dc-4d0d-9393-d8cbf8e6d036

📥 Commits

Reviewing files that changed from the base of the PR and between 24f9ebc and e70fd3c.

📒 Files selected for processing (3)
  • pkg/connector/client.go
  • pkg/connector/client_lifecycle_test.go
  • pkg/connector/connector.go

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved session/token recovery ordering around logout, forced logout, and cancellation.
    • Prevented superseded/retired sessions from restoring tokens or emitting outdated “connected” updates.
    • Ensured active connect work is promptly cancelled on disconnect/forced logout, without unintended relogins.
    • Made receive-auth health probing more reliable during reconnects and quiet periods.
    • Network/RPC/SSE operations now stop promptly when cancelled.
  • Improvements
    • Management-room bridge status notices are now disabled by default.
    • Bridge state updates no longer create or message management rooms.

Walkthrough

The PR adds context-aware LINE RPC calls, cancellable client runs, coordinated token recovery and forced logout handling, login replacement retirement, and independent receive-auth probing across SSE reconnects. Tests cover lifecycle races, cancellation, session invalidation, bridge-state behavior, and probe scheduling.

Changes

LINE RPC cancellation

Layer / File(s) Summary
Context-aware RPC calls
pkg/line/client.go, pkg/line/methods.go, pkg/line/methods_context_test.go, pkg/line/sse_test.go
RPC requests, profile lookup, revision lookup, and SSE listening now honor context cancellation, with cancellation tests for HTTP and SSE operations.

Client authentication lifecycle

Layer / File(s) Summary
Cancellable client runs and token recovery
pkg/connector/client.go, pkg/connector/auth_recovery_test.go, pkg/connector/client_lifecycle_test.go, pkg/connector/forced_logout_test.go
Connect and Disconnect manage cancellable runs; token recovery checks cancellation, supersession, and invalidated sessions; forced logout clears tokens and cancels active work without relogin.
Login replacement and bridge-state wiring
pkg/connector/connector.go, pkg/connector/connector_test.go, pkg/connector/forced_logout_test.go
Override logins retain and retire previous clients, bridge status notices are disabled, and bridge-state updates avoid management-room operations.

Receive authentication probing

Layer / File(s) Summary
Probe scheduling and SSE reconnects
pkg/connector/sync.go
The polling loop schedules receive-auth probes independently of SSE heartbeats, coordinates reconnect delays, preserves revisions after successful probes, and stops on cancellation or invalidation.
Probe timing and cancellation tests
pkg/connector/sync_test.go
Tests cover deterministic deadlines, heartbeat and EOF reconnect behavior, successful revision probes, parent cancellation, and cancellation during profile validation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Connect
  participant LineClient
  participant recoverTokenWith
  participant markLoggedOutByOtherClient
  participant pollLoop
  Connect->>LineClient: begin cancellable run
  LineClient->>recoverTokenWith: validate or recover token
  pollLoop->>LineClient: run receive-auth probe
  markLoggedOutByOtherClient->>LineClient: invalidate session and clear tokens
  LineClient-->>Connect: cancel active work
  recoverTokenWith-->>pollLoop: return cancellation or invalidation
Loading

Possibly related PRs

  • beeper/line#206: Updates the same SSE polling and reconnect flow with related concurrency handling.
  • beeper/line#211: Changes receive-auth failure handling and token recovery in sync.go.
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too generic and reads like a branch name rather than a clear summary of the change. Rename it to a concise summary of the main fix, such as reconnect lifecycle and token recovery improvements.
Description check ❓ Inconclusive No pull request description was provided, so there is nothing meaningful to assess. Add a short description of the reconnect and token-recovery changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch highest/fix-reconnect

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Comment @coderabbitai help to get the list of available commands.

Comment thread pkg/connector/client.go Outdated
// Keep connection state in Beeper's bridge status API only. Framework status
// notices call GetManagementRoom, which creates the LINE bot room when the
// user doesn't already have one.
bridge.Config.BridgeStatusNotices = "none"

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.

Consider only forcing "none" when the user hasn't configured a value — as written this silently overrides errors/all for anyone who explicitly configured management-room notices, and there's no config knob to opt back in. Something like:

Suggested change
bridge.Config.BridgeStatusNotices = "none"
if bridge.Config.BridgeStatusNotices == "" {
bridge.Config.BridgeStatusNotices = "none"
}

If the intent really is to force-off for all deployments regardless of user config, that's fine but probably worth a doc note; if not, this preserves user intent while still defaulting off. connector_test.TestInitDisablesManagementRoomBridgeStatusNotices will need to be adjusted.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/connector/sync.go (1)

1324-1327: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Redundant branch: both outcomes continue.

waitForSSEReconnect(receiveCtx) returns false (ctx done: parent-cancel or probe-due) or true (delay elapsed), but both paths continue, so the return value is discarded. This is correct (the loop top re-checks ctx.Err() and isReceiveAuthProbeDue), but the split reads like a missing return and invites incorrect future edits in this reconnect path. Collapse it.

♻️ Simplify the idle-timeout reconnect path
 			if line.IsSSEIdleTimeout(err) {
 				if probeAndReschedule() {
 					return
 				}
-				if !waitForSSEReconnect(receiveCtx) {
-					continue
-				}
+				waitForSSEReconnect(receiveCtx)
 				continue
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/connector/sync.go` around lines 1324 - 1327, Collapse the redundant
conditional in the reconnect path by removing the `if
!waitForSSEReconnect(receiveCtx)` branch and invoking
`waitForSSEReconnect(receiveCtx)` directly, followed by the existing `continue`;
preserve the loop behavior and subsequent context/probe checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/connector/sync.go`:
- Around line 1324-1327: Collapse the redundant conditional in the reconnect
path by removing the `if !waitForSSEReconnect(receiveCtx)` branch and invoking
`waitForSSEReconnect(receiveCtx)` directly, followed by the existing `continue`;
preserve the loop behavior and subsequent context/probe checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e89d0074-7995-478d-a18d-486c700a15d5

📥 Commits

Reviewing files that changed from the base of the PR and between 0f8ea15 and 24f9ebc.

📒 Files selected for processing (12)
  • pkg/connector/auth_recovery_test.go
  • pkg/connector/client.go
  • pkg/connector/client_lifecycle_test.go
  • pkg/connector/connector.go
  • pkg/connector/connector_test.go
  • pkg/connector/forced_logout_test.go
  • pkg/connector/sync.go
  • pkg/connector/sync_test.go
  • pkg/line/client.go
  • pkg/line/methods.go
  • pkg/line/methods_context_test.go
  • pkg/line/sse_test.go
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Lint with 1.25
  • GitHub Check: build-docker
  • GitHub Check: Lint with 1.25
  • GitHub Check: build-docker
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Use go fmt for code formatting across all Go files
Use goimports with -local "github.com/highesttt/matrix-line-messenger" flag to group project-local imports correctly
Use zerolog for logging throughout the codebase
Do not use Msgf in logging; use Msg with structured fields instead
Use Stringer interface where applicable in Go code

Files:

  • pkg/line/methods_context_test.go
  • pkg/line/sse_test.go
  • pkg/line/client.go
  • pkg/connector/connector_test.go
  • pkg/connector/auth_recovery_test.go
  • pkg/line/methods.go
  • pkg/connector/client_lifecycle_test.go
  • pkg/connector/sync.go
  • pkg/connector/sync_test.go
  • pkg/connector/client.go
  • pkg/connector/connector.go
  • pkg/connector/forced_logout_test.go
**/!(ltsm)/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/!(ltsm)/**/*.go: Run staticcheck on all Go files excluding pkg/ltsm package (transpiled WASM code)
Run go vet on all Go files excluding pkg/ltsm package (transpiled WASM code)

Files:

  • pkg/line/methods_context_test.go
  • pkg/line/sse_test.go
  • pkg/line/client.go
  • pkg/connector/connector_test.go
  • pkg/connector/auth_recovery_test.go
  • pkg/line/methods.go
  • pkg/connector/client_lifecycle_test.go
  • pkg/connector/sync.go
  • pkg/connector/sync_test.go
  • pkg/connector/client.go
  • pkg/connector/connector.go
  • pkg/connector/forced_logout_test.go
pkg/connector/connector.go

📄 CodeRabbit inference engine (AGENTS.md)

Implement bridgev2.NetworkConnector and bridgev2.NetworkAPI interfaces in the connector package for bridge logic

Files:

  • pkg/connector/connector.go
🔇 Additional comments (32)
pkg/connector/sync.go (5)

1164-1196: LGTM!


1198-1238: LGTM!


1291-1323: LGTM!

Also applies to: 1341-1346


1348-1395: LGTM!


1400-1444: LGTM!

pkg/connector/sync_test.go (5)

16-95: LGTM!


107-107: LGTM!

Also applies to: 157-162, 207-207, 260-260


317-424: LGTM!


426-518: LGTM!


520-526: LGTM!

Also applies to: 552-552, 576-613

pkg/line/client.go (1)

313-382: LGTM!

pkg/line/methods.go (1)

4-4: LGTM!

Also applies to: 103-124, 618-646

pkg/line/methods_context_test.go (1)

1-58: LGTM!

pkg/line/sse_test.go (2)

76-137: LGTM! Solid coverage of cancellation-with-cause semantics and heartbeat consumption timing.


107-111: 🎯 Functional Correctness

Confirm the module targets Go 1.22+

for range 5 depends on range-over-integer syntax, so this test needs the module’s go directive to be 1.22 or newer.

pkg/connector/connector_test.go (1)

1-167: LGTM!

pkg/connector/client.go (5)

62-67: LGTM!

Also applies to: 86-89, 155-198


316-328: LGTM!

Forced-logout/connected-state gating correctly serializes via recoverMu, and the superseded/UserLogin == nil guards prevent a stale/retired client from mutating a replacement's shared UserLogin state (validated by TestStaleForcedLogoutCancelsOnlyStaleClient).

Also applies to: 330-381


507-654: LGTM!

Connect's cancellable-run wiring (beginRun, workersStarted guard, ctx.Err() checks around token validation/startup) correctly ties worker startup to the run context and cleans up via run.cancel() on early return.


776-812: LGTM!

ensureValidTokenWith/Disconnect correctly delegate to the shared recoverTokenWith/recoverMu serialization, and Disconnect's final recoverMu.Lock()/Unlock() after wg.Wait() correctly drains any recovery/forced-logout call that began before superseded became visible.

Also applies to: 814-832


443-505: 🩺 Stability & Availability

recoverMu is held across the full refresh/re-login round-trip, so a slow LINE call can block Disconnect()/retire() on the reconnect path. Confirm this is an intentional trade-off and that both recovery calls are always bounded by ctx/HTTP timeouts.

pkg/connector/forced_logout_test.go (3)

5-13: LGTM!

Also applies to: 27-27, 52-74


76-127: LGTM!

Good deterministic race test for "forced logout wins" specifically through the ensureValidTokenWith refresh path, complementing the direct runTokenRecovery version in auth_recovery_test.go.


155-171: LGTM!

Correctly asserts ExistingLogin retention needed for the retire-before-replace flow in connector.go's finishLogin.

pkg/connector/auth_recovery_test.go (2)

248-262: LGTM!

Also applies to: 264-281, 283-304


306-342: LGTM!

Solid coverage of the "forced logout must win over an in-flight recovery" invariant via recoverMu serialization.

pkg/connector/client_lifecycle_test.go (2)

1-175: LGTM!

Good coverage of beginRun/Disconnect race ordering (reservation-before-publish, cancel-then-join), stale-vs-current client isolation, and idempotent claimForcedLogoutState under concurrent access.

Also applies to: 194-194


176-193: 📐 Maintainability & Code Quality

Confirm the module targets Go 1.22+ for for range 32. for range 32 needs Go 1.22+, so this test will not compile if the module or toolchain is pinned lower.

pkg/connector/connector.go (4)

166-166: LGTM!

Also applies to: 209-209


467-474: LGTM!

Retiring the previous client before NewLogin copies in the freshly-authenticated metadata correctly prevents a late forced-logout/recovery from the old client clobbering the replacement session (see the root-cause note on recoverMu hold time in client.go's runTokenRecovery, which bounds how long this blocking call can take).


496-497: LGTM!

Dropping the direct StateConnected emission here is consistent with moving that responsibility to sendConnectedStateIfCurrent, which correctly suppresses it for superseded/invalidated clients.


35-39: 🗄️ Data Integrity & Integration

BridgeStatusNotices = "none" is correct none is the documented value for disabling bridge status notices, so this line is fine.

			> Likely an incorrect or invalid review comment.

@highesttt highesttt merged commit 717f42f into main Jul 10, 2026
9 checks passed
@indent

indent Bot commented Jul 10, 2026

Copy link
Copy Markdown
PR Summary

Generating summary...

Issues

Review closed.

CI Checks

2 CI checks failed on 24f9ebc.

Failing Lint with 1.25
  • Lint with 1.25 finished with failure.
Failing Lint with 1.25
  • Lint with 1.25 finished with failure.

@highesttt highesttt deleted the highest/fix-reconnect branch July 10, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant