Skip to content

Recover LINE auth errors at runtime#188

Merged
highesttt merged 5 commits into
beeper:mainfrom
Adri11334:line-auth-recovery-wrapper
Jun 28, 2026
Merged

Recover LINE auth errors at runtime#188
highesttt merged 5 commits into
beeper:mainfrom
Adri11334:line-auth-recovery-wrapper

Conversation

@Adri11334

@Adri11334 Adri11334 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes runtime LINE auth recovery for API paths that can currently return expired-token errors directly while the bridge is already running.

When LINE returns an auth error such as TalkException code 119 / Access token refresh required, the bridge now attempts token recovery, recreates the LINE client with the recovered token, and retries the failed call once.

Fixes #163.

Changes

  • add shared LINE auth error classifiers for refresh-required, logged-out, and 401/403 API/HTTP/SSE/OBS failures
  • add a centralized LINE call wrapper that refreshes/re-logins on auth errors and retries once
  • use the wrapper for outbound sends, media uploads, reactions, read receipts, chat leave/invite actions, media-flow lookup, and related user info calls
  • add the same auth recovery retry to inbound LINE file/media downloads that can fail with OBS auth errors
  • coalesce concurrent token recoveries so multiple expired-token calls do not all refresh at once
  • synchronize access-token reads and writes used by runtime recovery to avoid races during concurrent LINE calls
  • keep generic network errors as non-auth failures so they are returned without token recovery or duplicate retries
  • remove the repo-level *_test.go ignore so the new auth recovery regression tests are tracked

Testing

  • GOCACHE=/private/tmp/line-gocache GOWORK=off go test ./pkg/line ./pkg/connector ./pkg/connector/handlers
  • docker run --rm -v "$PWD":/build -w /build alpine:3.23 sh -lc 'apk add --no-cache go git build-base olm-dev staticcheck && go test ./... && go vet $(go list ./... | grep -v /ltsm) && staticcheck $(go list ./... | grep -v /ltsm)'
  • GOCACHE=/private/tmp/line-gocache GOWORK=off go test -race ./pkg/connector
  • docker build -t matrix-line-auth-recovery .

Copilot AI review requested due to automatic review settings June 17, 2026 01:18
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Adri11334, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 8 seconds. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b43a11e-b007-4279-a6ee-f88e9caa2d1c

📥 Commits

Reviewing files that changed from the base of the PR and between 2afdad9 and 7366136.

📒 Files selected for processing (1)
  • pkg/connector/userinfo.go
📝 Walkthrough

Walkthrough

Adds LINE auth classification, shared retry-on-recovery call helpers, and recovery state on LineClient. Connector LINE callsites are updated to use the shared client factory and wrappers for message, media, sync, E2EE, and user-info flows.

Changes

LINE Auth Recovery and Client Reuse

Layer / File(s) Summary
Auth error classification
pkg/line/errors.go, pkg/line/errors_test.go, .gitignore
Adds IsRefreshRequired, IsLoggedOut, IsUnauthorizedStatus, and IsAuthError with tests for TalkException, logged-out, and unauthorized-status strings. Removes the .gitignore entry that excluded *_test.go files.
Shared recovery helper and token state
pkg/connector/auth_recovery.go, pkg/connector/client.go, pkg/connector/handle_message.go
Adds callLineWithRecovery, isTokenError, and the callLine/callLineResult wrappers. Adds recovery mutex/time state to LineClient, centralizes token access, refactors refresh/relogin recovery, and routes media-flow detection and message-handler client creation through lc.newClient().
Outbound message and action callsites
pkg/connector/handlers/handler.go, pkg/connector/reaction.go, pkg/connector/send_message.go, pkg/connector/handlers/*
Updates recovery gating to use unauthorized-status classification and routes reaction, send, remove, leave, accept, and OBS download/upload paths through the shared LINE call helpers.
Lookup, sync, and E2EE client reuse
pkg/connector/creategroup.go, pkg/connector/e2ee_keys.go, pkg/connector/sync.go, pkg/connector/userinfo.go
Switches group creation, E2EE key flows, user-info lookup, and sync/poll loops to build LINE clients through lc.newClient() in initial and retry branches after token recovery.
Recovery tests
pkg/connector/auth_recovery_test.go
Adds coverage for retry behavior, client reuse, token-error exclusion for E2EE key errors, recovery-window skipping, and serialized concurrent recovery.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: runtime recovery from LINE auth errors.
Description check ✅ Passed The description matches the code changes and issue goal of runtime LINE token recovery.
Linked Issues check ✅ Passed The changes address #163 by retrying LINE calls after auth failures and refreshing or relogging in.
Out of Scope Changes check ✅ Passed The added helpers, retries, tests, and ignore-file tweak all support the auth-recovery objective.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copilot AI 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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR introduces centralized token/auth recovery for LINE API calls and adds structured helpers to classify auth-related errors, then refactors connector operations to use the new recovery path.

Changes:

  • Added line.IsRefreshRequired, line.IsLoggedOut, line.IsUnauthorizedStatus, and line.IsAuthError plus unit tests.
  • Implemented generic callLineWithRecovery + LineClient.callLine wrappers and refactored multiple connector handlers to use them.
  • Added concurrency controls and a short “recent recovery” window to avoid repeated token recovery attempts.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/line/errors.go Adds helper predicates to detect auth/unauthorized/refresh-required errors.
pkg/line/errors_test.go Adds unit tests for new error classification helpers.
pkg/connector/auth_recovery.go Introduces generic LINE call wrapper with token recovery + retry.
pkg/connector/auth_recovery_test.go Adds tests for retry/recovery behavior and concurrency serialization.
pkg/connector/client.go Routes refresh detection through line helpers and serializes recovery with mutex/window.
pkg/connector/send_message.go Refactors message sending/uploads to use recovery wrapper instead of direct client calls.
pkg/connector/userinfo.go Uses lc.callLine for read receipts.
pkg/connector/reaction.go Uses lc.callLine for reactions and cancellations.
pkg/connector/handlers/handler.go Switches recovery trigger from substring "401" to line.IsUnauthorizedStatus.
pkg/connector/handlers/file.go Retries OBS download after token recovery.
.gitignore Stops ignoring *_test.go so tests can be committed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/connector/handlers/file.go Outdated
Comment thread pkg/connector/send_message.go Outdated
Comment thread pkg/connector/send_message.go Outdated
Comment thread pkg/line/errors.go Outdated

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
pkg/line/errors.go (1)

22-24: ⚡ Quick win

Harden auth classifiers against case-variant error strings.

These predicates drive token-recovery retry; case-sensitive matching can miss recoverable auth errors if upstream text casing drifts.

Suggested patch
 func IsRefreshRequired(err error) bool {
 	if err == nil {
 		return false
 	}
-	msg := err.Error()
+	msg := strings.ToLower(err.Error())
 	return strings.Contains(msg, "\"code\":119") ||
-		strings.Contains(msg, "Access token refresh required")
+		strings.Contains(msg, "access token refresh required")
 }

 func IsLoggedOut(err error) bool {
 	if err == nil {
 		return false
 	}
-	return strings.Contains(err.Error(), "V3_TOKEN_CLIENT_LOGGED_OUT")
+	return strings.Contains(strings.ToLower(err.Error()), "v3_token_client_logged_out")
 }

 func IsUnauthorizedStatus(err error) bool {
 	if err == nil {
 		return false
 	}
-	msg := err.Error()
-	return strings.Contains(msg, "API error 401") ||
-		strings.Contains(msg, "API error 403") ||
-		strings.Contains(msg, "HTTP 401") ||
-		strings.Contains(msg, "HTTP 403") ||
-		strings.Contains(msg, "SSE error: 401") ||
-		strings.Contains(msg, "SSE error: 403") ||
-		strings.Contains(msg, "OBS upload failed (401)") ||
-		strings.Contains(msg, "OBS upload failed (403)") ||
-		strings.Contains(msg, "OBS download failed (401)") ||
-		strings.Contains(msg, "OBS download failed (403)")
+	msg := strings.ToLower(err.Error())
+	return strings.Contains(msg, "api error 401") ||
+		strings.Contains(msg, "api error 403") ||
+		strings.Contains(msg, "http 401") ||
+		strings.Contains(msg, "http 403") ||
+		strings.Contains(msg, "sse error: 401") ||
+		strings.Contains(msg, "sse error: 403") ||
+		strings.Contains(msg, "obs upload failed (401)") ||
+		strings.Contains(msg, "obs upload failed (403)") ||
+		strings.Contains(msg, "obs download failed (401)") ||
+		strings.Contains(msg, "obs download failed (403)")
 }

Also applies to: 31-31, 38-48

🤖 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/line/errors.go` around lines 22 - 24, The error string matching in this
function is case-sensitive, which can cause recoverable auth errors to be missed
if the upstream error messages have different casing. Convert the error message
to lowercase using strings.ToLower() before performing the strings.Contains()
checks against both the "code":119 pattern and the "Access token refresh
required" string. Apply the same case-insensitive matching fix to all other
similar error classification predicates mentioned in the file at the referenced
line ranges.
pkg/connector/auth_recovery.go (1)

24-27: ⚡ Quick win

Preserve the triggering auth error when recovery fails.

The returned error currently drops the original LINE call failure context, which makes incident triage harder.

Suggested patch
-		return client, zero, fmt.Errorf("failed to recover token after LINE auth error: %w", errRecover)
+		return client, zero, fmt.Errorf("LINE auth call failed (%v); token recovery failed: %w", err, errRecover)
🤖 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/auth_recovery.go` around lines 24 - 27, The error returned when
token recovery fails only preserves the recovery error from errRecover but loses
the original LINE authentication error that triggered the recovery attempt.
Modify the recovery error handling block to capture the original authentication
error before calling deps.recover(ctx), and then when errRecover is not nil,
wrap both errors together in the returned error using fmt.Errorf or error
wrapping to ensure both the original LINE auth error and the recovery error are
included in the error chain for better incident triage.
🤖 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.

Inline comments:
In `@pkg/connector/client.go`:
- Around line 33-34: The AccessToken field is read concurrently in
auth_recovery.go (in the newClient closures at Lines 43 and 55) while being
mutated in refresh/login paths, creating a data race condition. Add a new
tokenMu sync.RWMutex field to the LineClient struct to synchronize all token
access. Create a getAccessToken() method that acquires a read lock and returns
the AccessToken value. Update auth_recovery.go to use this getAccessToken()
getter instead of directly accessing AccessToken. Guard all token write
operations in refreshAndSave and tryLogin methods by acquiring a write lock on
tokenMu before modifying AccessToken. This ensures consistent synchronization of
token state across concurrent operations.

---

Nitpick comments:
In `@pkg/connector/auth_recovery.go`:
- Around line 24-27: The error returned when token recovery fails only preserves
the recovery error from errRecover but loses the original LINE authentication
error that triggered the recovery attempt. Modify the recovery error handling
block to capture the original authentication error before calling
deps.recover(ctx), and then when errRecover is not nil, wrap both errors
together in the returned error using fmt.Errorf or error wrapping to ensure both
the original LINE auth error and the recovery error are included in the error
chain for better incident triage.

In `@pkg/line/errors.go`:
- Around line 22-24: The error string matching in this function is
case-sensitive, which can cause recoverable auth errors to be missed if the
upstream error messages have different casing. Convert the error message to
lowercase using strings.ToLower() before performing the strings.Contains()
checks against both the "code":119 pattern and the "Access token refresh
required" string. Apply the same case-insensitive matching fix to all other
similar error classification predicates mentioned in the file at the referenced
line ranges.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a074534e-d455-4510-acf7-d316680eba28

📥 Commits

Reviewing files that changed from the base of the PR and between 593cd2d and 30c425d.

📒 Files selected for processing (11)
  • .gitignore
  • pkg/connector/auth_recovery.go
  • pkg/connector/auth_recovery_test.go
  • pkg/connector/client.go
  • pkg/connector/handlers/file.go
  • pkg/connector/handlers/handler.go
  • pkg/connector/reaction.go
  • pkg/connector/send_message.go
  • pkg/connector/userinfo.go
  • pkg/line/errors.go
  • pkg/line/errors_test.go
💤 Files with no reviewable changes (1)
  • .gitignore
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/connector/reaction.go
  • pkg/connector/handlers/file.go
  • pkg/connector/userinfo.go
  • pkg/line/errors_test.go
  • pkg/connector/auth_recovery_test.go
  • pkg/connector/auth_recovery.go
  • pkg/connector/handlers/handler.go
  • pkg/line/errors.go
  • pkg/connector/client.go
  • pkg/connector/send_message.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/connector/reaction.go
  • pkg/connector/handlers/file.go
  • pkg/connector/userinfo.go
  • pkg/line/errors_test.go
  • pkg/connector/auth_recovery_test.go
  • pkg/connector/auth_recovery.go
  • pkg/connector/handlers/handler.go
  • pkg/line/errors.go
  • pkg/connector/client.go
  • pkg/connector/send_message.go
🔇 Additional comments (9)
pkg/connector/auth_recovery_test.go (1)

21-120: LGTM!

Also applies to: 122-133, 135-149, 151-194

pkg/line/errors_test.go (1)

8-84: LGTM!

pkg/connector/handlers/handler.go (1)

35-35: LGTM!

pkg/connector/send_message.go (1)

34-52: LGTM!

Also applies to: 226-228, 240-242, 328-330, 411-413, 421-423, 449-451, 513-515, 657-657, 682-684, 719-719, 746-746, 770-772, 783-785, 837-839, 858-862, 866-869, 879-882

pkg/connector/userinfo.go (1)

40-43: LGTM!

pkg/line/errors.go (1)

1-8: Run the required Go hygiene checks for this PR cohort.

Please verify formatting, import grouping, and static analysis compliance across changed Go packages (excluding pkg/ltsm). Run the following checks:

  • gofmt for code formatting
  • goimports -local "github.com/highesttt/matrix-line-messenger" for import grouping
  • rg -n --type=go '\.Msgf\s*\(' to scan for prohibited Msgf usage in zerolog
  • go vet and staticcheck on all packages except pkg/ltsm

As per coding guidelines, ensure compliance with go fmt, goimports with local grouping, Msg (not Msgf) for structured logging, and static analysis for all Go code outside the WASM package.

pkg/connector/handlers/file.go (1)

41-44: LGTM!

pkg/connector/reaction.go (1)

369-372: LGTM!

Also applies to: 397-399

pkg/connector/auth_recovery.go (1)

1-8: Run mandatory Go quality checks locally before merging.

Per coding guidelines, the following checks are required for all Go files (excluding pkg/ltsm):

  • go fmt for formatting
  • goimports -local "github.com/highesttt/matrix-line-messenger" for import grouping
  • go vet for static analysis
  • staticcheck for linting

These tools must be executed in your development environment to verify compliance before this change is merged.

Comment thread pkg/connector/client.go
@highesttt

Copy link
Copy Markdown
Collaborator

@clins1994 Could you check this one out and get back to me in a week to see if that fixes your issue

@clins1994

Copy link
Copy Markdown
Contributor

@Adri11334 thanks for working on it. i'll leave this running on my bridge so we can verify. let's check back in 7 days cc @highesttt

@highesttt

Copy link
Copy Markdown
Collaborator

@Adri11334 thanks for working on it. i'll leave this running on my bridge so we can verify. let's check back in 7 days cc @highesttt

Awesome. Thank you so much! Looking forward to seeing your feedback om this

@clins1994

Copy link
Copy Markdown
Contributor

@Adri11334 @highesttt good news is it seems to be working

  - 2026-06-18 02:01:12: docker image was built
  - 2026-06-18 02:01:15: docker container was created from that image
  - 2026-06-18 02:01:20: bridge initialized and started running the deployed build.
  - 2026-06-24 02:14:46: runtime recovery evidence appears. During a Matrix-to-LINE message, the bridge logged: Refresh failed, attempting re-login with stored credentials...
  - 2026-06-24 02:14:47: Re-login succeeded automatically: Login successful!
  - 2026-06-24 *****************: The same message flow completed with Beeper send status SUCCESS.
  - 2026-06-25 02:20:32: Logs still show normal bridge/app activity, with no auth failure markers afterward.

so the key evidence happened about 6 days after deploy, and by June 25 the container had passed the ~7-day mark without requiring manual login

@highesttt

Copy link
Copy Markdown
Collaborator

@Adri11334 can you pull from main and make sure it compiles and still runs, just making sure nothing breaks.
Ill merge after that

@Adri11334

Copy link
Copy Markdown
Contributor Author

Pulled latest main into this branch and reran the local checks.

  • go test ./...
  • go vet $(go list ./... | grep -v /ltsm)
  • staticcheck $(go list ./... | grep -v /ltsm)
  • go test -race ./pkg/connector
  • goimports
  • docker build -t matrix-line-auth-recovery .
  • docker run --rm matrix-line-auth-recovery matrix-line --help

Everything passes on my side.

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

Actionable comments posted: 3

🤖 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.

Inline comments:
In `@pkg/connector/client.go`:
- Around line 347-355: FullSync still starts message delivery before portal
creation, so the new portal-first ordering is bypassed. Update the fullSync path
in pkg/connector/sync.go so syncChats runs and completes before syncDMChats and
prefetchMessages are launched, matching the ordering used in Connect and
preventing first-message portal creation from making existing membership look
like a fresh join.

In `@pkg/connector/handle_message.go`:
- Around line 117-153: The hidden join is being emitted twice for a first-seen
sender because ensureGroupMessageSenderKnown already queues it and the Message
PreHandleFunc in handleMessage repeats it once the portal exists. Update the
flow so ensureGroupMessageSenderKnown stays cache-only and remove the duplicate
hidden member update from the Message path, keeping the actual join emission in
one place such as hiddenJoinGroupMessageSender.

In `@pkg/connector/userinfo.go`:
- Around line 319-320: The LINE user-id lookup path in the contact search flow
still skips refresh-required/logged-out recovery; update the logic around
client.FindContactByUserid in the search routine to detect token-expired
failures, trigger the same token recovery/re-login path used by the later
contact-list lookup, and then retry the exact user-id search before returning no
result. Use the existing search method and login recovery helpers in
lc.newClient(), FindContactByUserid, and the surrounding contact lookup flow so
non-contact LINE IDs are retried after token recovery.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb65fbd5-86ec-4bd2-b319-ade85fbe8fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 30c425d and 2afdad9.

📒 Files selected for processing (14)
  • pkg/connector/auth_recovery.go
  • pkg/connector/auth_recovery_test.go
  • pkg/connector/client.go
  • pkg/connector/creategroup.go
  • pkg/connector/e2ee_keys.go
  • pkg/connector/handle_message.go
  • pkg/connector/handlers/audio.go
  • pkg/connector/handlers/file.go
  • pkg/connector/handlers/video.go
  • pkg/connector/send_message.go
  • pkg/connector/sync.go
  • pkg/connector/userinfo.go
  • pkg/line/errors.go
  • pkg/line/errors_test.go
💤 Files with no reviewable changes (3)
  • pkg/connector/handlers/file.go
  • pkg/connector/handlers/video.go
  • pkg/connector/handlers/audio.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • pkg/line/errors.go
  • pkg/line/errors_test.go
  • pkg/connector/auth_recovery_test.go
  • pkg/connector/auth_recovery.go
  • pkg/connector/send_message.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/connector/handle_message.go
  • pkg/connector/creategroup.go
  • pkg/connector/userinfo.go
  • pkg/connector/e2ee_keys.go
  • pkg/connector/client.go
  • pkg/connector/sync.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/connector/handle_message.go
  • pkg/connector/creategroup.go
  • pkg/connector/userinfo.go
  • pkg/connector/e2ee_keys.go
  • pkg/connector/client.go
  • pkg/connector/sync.go
🔇 Additional comments (1)
pkg/connector/sync.go (1)

524-535: 🎯 Functional Correctness

Immediate post-queue lookup can miss new portals
This only works if QueueRemoteEvent(...) makes the portal visible before returning. If portal creation is deferred, GetExistingPortalByKey(...) can return nil here, pendingPortalCreates will skip that chat, and waitForGroupPortalCreates(...) won’t block prefetch/SSE for it.

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🤖 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.

Inline comments:
In `@pkg/connector/client.go`:
- Around line 347-355: FullSync still starts message delivery before portal
creation, so the new portal-first ordering is bypassed. Update the fullSync path
in pkg/connector/sync.go so syncChats runs and completes before syncDMChats and
prefetchMessages are launched, matching the ordering used in Connect and
preventing first-message portal creation from making existing membership look
like a fresh join.

In `@pkg/connector/handle_message.go`:
- Around line 117-153: The hidden join is being emitted twice for a first-seen
sender because ensureGroupMessageSenderKnown already queues it and the Message
PreHandleFunc in handleMessage repeats it once the portal exists. Update the
flow so ensureGroupMessageSenderKnown stays cache-only and remove the duplicate
hidden member update from the Message path, keeping the actual join emission in
one place such as hiddenJoinGroupMessageSender.

In `@pkg/connector/userinfo.go`:
- Around line 319-320: The LINE user-id lookup path in the contact search flow
still skips refresh-required/logged-out recovery; update the logic around
client.FindContactByUserid in the search routine to detect token-expired
failures, trigger the same token recovery/re-login path used by the later
contact-list lookup, and then retry the exact user-id search before returning no
result. Use the existing search method and login recovery helpers in
lc.newClient(), FindContactByUserid, and the surrounding contact lookup flow so
non-contact LINE IDs are retried after token recovery.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb65fbd5-86ec-4bd2-b319-ade85fbe8fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 30c425d and 2afdad9.

📒 Files selected for processing (14)
  • pkg/connector/auth_recovery.go
  • pkg/connector/auth_recovery_test.go
  • pkg/connector/client.go
  • pkg/connector/creategroup.go
  • pkg/connector/e2ee_keys.go
  • pkg/connector/handle_message.go
  • pkg/connector/handlers/audio.go
  • pkg/connector/handlers/file.go
  • pkg/connector/handlers/video.go
  • pkg/connector/send_message.go
  • pkg/connector/sync.go
  • pkg/connector/userinfo.go
  • pkg/line/errors.go
  • pkg/line/errors_test.go
💤 Files with no reviewable changes (3)
  • pkg/connector/handlers/file.go
  • pkg/connector/handlers/video.go
  • pkg/connector/handlers/audio.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • pkg/line/errors.go
  • pkg/line/errors_test.go
  • pkg/connector/auth_recovery_test.go
  • pkg/connector/auth_recovery.go
  • pkg/connector/send_message.go
📜 Review details
🔇 Additional comments (1)
pkg/connector/sync.go (1)

524-535: 🎯 Functional Correctness

Immediate post-queue lookup can miss new portals
This only works if QueueRemoteEvent(...) makes the portal visible before returning. If portal creation is deferred, GetExistingPortalByKey(...) can return nil here, pendingPortalCreates will skip that chat, and waitForGroupPortalCreates(...) won’t block prefetch/SSE for it.

🛑 Comments failed to post (3)
pkg/connector/client.go (1)

347-355: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

FullSync still bypasses the new portal-first ordering.

Connect now blocks on syncChats before prefetch/polling, but pkg/connector/sync.go Line 1121-1124 still starts syncChats, syncDMChats, and prefetchMessages together on fullSync. That means a later full sync can still let the first delivered group message create the portal and make existing membership look like a fresh join again.

🤖 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/client.go` around lines 347 - 355, FullSync still starts
message delivery before portal creation, so the new portal-first ordering is
bypassed. Update the fullSync path in pkg/connector/sync.go so syncChats runs
and completes before syncDMChats and prefetchMessages are launched, matching the
ordering used in Connect and preventing first-message portal creation from
making existing membership look like a fresh join.
pkg/connector/handle_message.go (1)

117-153: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid emitting the hidden join twice for a newly seen sender.

ensureGroupMessageSenderKnown(...) already queues a join when the sender is first added to the cache, and PreHandleFunc emits the same hidden join again once the portal is available. Existing group portals will process both. Make the early path cache-only, and keep the actual hidden member update in one place.

🤖 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/handle_message.go` around lines 117 - 153, The hidden join is
being emitted twice for a first-seen sender because
ensureGroupMessageSenderKnown already queues it and the Message PreHandleFunc in
handleMessage repeats it once the portal exists. Update the flow so
ensureGroupMessageSenderKnown stays cache-only and remove the duplicate hidden
member update from the Message path, keeping the actual join emission in one
place such as hiddenJoinGroupMessageSender.
pkg/connector/userinfo.go (1)

319-320: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Retry exact LINE user-id searches after token recovery.

FindContactByUserid(...) is the one lookup path here that still ignores refresh-required/logged-out failures. When the token expires, searches for non-contact LINE IDs will return nothing even though the later contact-list path recovers correctly.

Suggested fix
 	if lowerQuery != "" {
 		client := lc.newClient()
 		contact, err := client.FindContactByUserid(lowerQuery)
+		if err != nil && (lc.isRefreshRequired(err) || lc.isLoggedOut(err)) {
+			if errRecover := lc.recoverToken(ctx); errRecover == nil {
+				client = lc.newClient()
+				contact, err = client.FindContactByUserid(lowerQuery)
+			}
+		}
 		if err == nil && contact != nil && contact.Mid != "" {
 			if r := lc.midToResolveIdentifier(ctx, contact.Mid); r != nil {
 				results = append(results, r)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

		client := lc.newClient()
		contact, err := client.FindContactByUserid(lowerQuery)
		if err != nil && (lc.isRefreshRequired(err) || lc.isLoggedOut(err)) {
			if errRecover := lc.recoverToken(ctx); errRecover == nil {
				client = lc.newClient()
				contact, err = client.FindContactByUserid(lowerQuery)
			}
		}
		if err == nil && contact != nil && contact.Mid != "" {
🤖 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/userinfo.go` around lines 319 - 320, The LINE user-id lookup
path in the contact search flow still skips refresh-required/logged-out
recovery; update the logic around client.FindContactByUserid in the search
routine to detect token-expired failures, trigger the same token
recovery/re-login path used by the later contact-list lookup, and then retry the
exact user-id search before returning no result. Use the existing search method
and login recovery helpers in lc.newClient(), FindContactByUserid, and the
surrounding contact lookup flow so non-contact LINE IDs are retried after token
recovery.

@highesttt highesttt merged commit 461e334 into beeper:main Jun 28, 2026
4 of 5 checks passed
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.

bug: 🐛 token refresh is not triggered causing need to re-login every ~7 days

4 participants