Skip to content

fix(channels): clear stale OAuth Connecting badges across auth modes (#2128)#2256

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/2128-oauth-badge-pending-state
May 20, 2026
Merged

fix(channels): clear stale OAuth Connecting badges across auth modes (#2128)#2256
senamakel merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/2128-oauth-badge-pending-state

Conversation

@sanil-23
Copy link
Copy Markdown
Contributor

@sanil-23 sanil-23 commented May 19, 2026

Summary

  • Centralises OAuth deep-link → channel-badge transitions behind a new
    useOAuthConnectionListener hook so every channel panel handles both
    oauth:success and oauth:error consistently.
  • Adds a clearOtherPendingForChannel reducer so starting a connect flow on
    one auth mode drops any sibling auth mode that's still mid-connecting on
    the same channel.
  • Wires DiscordConfig and TelegramConfig onto the shared hook; future
    channels with an OAuth auth mode inherit correct pending-state transitions
    automatically.
  • Covers the new reducer (4 cases) and hook (8 cases) with Vitest.

Problem

OAuth badges on the channel connection panels could get pinned at
Connecting indefinitely (issue #2128):

  • DiscordConfig had a per-component oauth:success listener but no
    oauth:error listener — failed OAuth attempts never transitioned the badge
    out of connecting.
  • TelegramConfig had neither — completed and failed OAuth attempts left
    the badge pinned.
  • Both panels set connecting on the chosen auth mode but never cancelled
    any sibling auth mode that was already pending. Triggering a second OAuth
    method on Discord (OAuth Sign-in then Login with OpenHuman, or the
    reverse) left both methods badged Connecting simultaneously.

This is the exact repro from the issue. The same shape was visible across
GitHub/GitLab style multi-method panels because the underlying state model
(channelConnections, keyed by (channel, authMode)) had no notion of
mutual exclusion.

Solution

Shared listener hook
app/src/hooks/useOAuthConnectionListener.ts
subscribes to both oauth:success and oauth:error window events
(dispatched from utils/desktopDeepLinkListener.ts), filters by toolkit /
provider case-insensitively, and dispatches the matching slice action.
Per-channel panels mount it once with { channel, authMode }; cleanup on
unmount is deterministic. New channels with an OAuth auth mode inherit the
behaviour without copying any logic.

Pending-state cancellation reducerclearOtherPendingForChannel({ channel, exceptAuthMode }) in channelConnectionsSlice.ts walks the auth-mode map
for one channel and transitions every connecting row (except the
exception) to disconnected with lastError: undefined. Cancelled rows go
to disconnected rather than error so the UI doesn't surface a misleading
failure — the user explicitly switched methods, they didn't experience an
error.

Per-panel wiringDiscordConfig and TelegramConfig each:

  1. Mount useOAuthConnectionListener({ channel: <name>, authMode: 'oauth' })
    at the top of the component (replacing the bespoke effect on Discord;
    net-new on Telegram).
  2. Dispatch clearOtherPendingForChannel at the start of handleConnect
    before setting their own auth mode to connecting.

Tradeoffs

  • The cancellation transition is disconnected, not a new cancelled state.
    Adding a dedicated state would expand the ChannelConnectionStatus union
    across many call sites for marginal UX value.
  • The deep-link CustomEvent payload ({ integrationId, toolkit } for
    success, { provider, errorCode, message } for error) is unchanged, so
    no symmetric change in the Tauri-side handler is needed.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy — 12 new Vitest cases (4 reducer + 8 hook) covering success, error, mismatched channel, mismatched provider, missing error message, custom capabilities, unsubscribe on unmount, and three sibling-cancellation shapes.
  • Diff coverage ≥ 80% — frontend-only change; pnpm test:coverage locally over the new files reaches 100% on changed lines (every branch in the hook + reducer is exercised by the suite).
  • Coverage matrix updated — N/A: behaviour-only fix on existing surfaces (channel connection pending state).
  • All affected feature IDs from the matrix are listed in the PR description under ## RelatedN/A: no feature ID changes.
  • No new external network dependencies introduced — purely in-app state plumbing.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: no release-cut surface touched (channels panel is part of the always-shipped settings UX).
  • Linked issue closed via Closes #NNN in the ## Related section — see below.

Impact

  • Desktop only — no mobile/web/CLI impact. The deep-link event source
    (desktopDeepLinkListener.ts) is Tauri-gated; the hook is a no-op outside
    Tauri because no deep-link events fire.
  • No persistence shape changechannelConnections slice schema
    (SCHEMA_VERSION = 1) is unchanged. The new reducer only mutates existing
    rows; no migration needed.
  • No security implications — the listener filters strictly by channel
    identifier and never reads tokens. Existing [DeepLink][oauth:*] logs
    remain the canonical diagnostic surface; the hook adds its own
    channels:oauth-listener debug namespace per the project's
    verbose-diagnostics rule.

Related

Provider coverage

The issue body mentions Discord, GitHub, and GitLab. The Channels page in this codebase only exposes three multi-method channel-config panels today: DiscordConfig.tsx, TelegramConfig.tsx, and WebChannelConfig.tsx (the last is not OAuth-driven). There is no GitHubConfig.tsx / GitLabConfig.tsx — verified via find app/src -name "*Config.tsx".

GitHub OAuth does appear elsewhere in the app, but on different state slices that this PR's channelConnections-bound hook does not (and should not) touch:

Surface File(s) State path This PR applies?
App-level sign-in BootCheckGate.tsx, OAuth callback deepLinkAuth slice No — different slice. App-level OAuth's hot-instance issue is the family fixed by #2228 / #2229.
Skill OAuth install InstallSkillDialog.tsx, services/api/skillsApi.ts skills-domain state No — different surface.
Composio integration components/composio/TriggerToggles.tsx, composio/providerConfigs.tsx Composio integration state No — different surface.
Channel config (this PR) DiscordConfig.tsx, TelegramConfig.tsx channelConnections slice Yes — wired.

So this PR's useOAuthConnectionListener covers every multi-method OAuth panel that actually exists on the Channels surface. The shared hook is also the right shape for any future GitHubConfig.tsx / GitLabConfig.tsx channel panels — wiring them in becomes a one-line useOAuthConnectionListener({ channelId, capabilities, ... }) import.

If the stale-Connecting symptom also surfaces in the app-level / skills / Composio OAuth flows, those are separate fixes against different state slices and out of scope for this PR — I'm happy to file follow-up issues if any are observed.


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/2128-oauth-badge-pending-state
  • Commit SHA: 2d93f7c0

Validation Run

  • pnpm --filter openhuman-app format:checkAll matched files use Prettier code style! on the 6 changed files
  • pnpm typecheck — clean (tsc --noEmit)
  • Focused tests: pnpm --filter openhuman-app exec vitest run --config test/vitest.config.ts src/store/__tests__/channelConnectionsSlice.test.ts src/hooks/__tests__/useOAuthConnectionListener.test.tsx src/components/channels/__tests__/DiscordConfig.test.tsx src/components/channels/__tests__/TelegramConfig.test.tsx → 4 files, 27 tests pass
  • Rust fmt/check (if changed): N/A: no Rust changes
  • Tauri fmt/check (if changed): N/A: no Tauri shell changes

Validation Blocked

  • command: git push pre-push hook (app:lint:commands-tokens)
  • error: lint:commands-tokens requires ripgreprg not installed on the dev environment
  • impact: zero — the check greps a directory I did not modify (src/components/commands/). Pushed with --no-verify per the CLAUDE.md guidance for environment-related hook failures unrelated to the diff. Maintainers can re-run on CI to validate.

Behavior Changes

  • Intended behavior change: OAuth badges on channel panels transition out of connecting when the OAuth flow completes or fails, and starting a new method cancels the previous method's connecting row.
  • User-visible effect: the reported bug (multiple methods stuck on Connecting simultaneously, Telegram OAuth never clearing) goes away. No new UI elements; only badge state transitions are affected.

Parity Contract

  • Legacy behavior preserved: existing connected and error transitions are unchanged; disconnectChannelConnection, upsertChannelConnection, setChannelConnectionStatus are all untouched. The Discord oauth:success path still produces the same final state (status: 'connected', capabilities: ['read', 'write']); the inline effect was just refactored behind the shared hook.
  • Guard/fallback/dispatch parity checks: hook only reacts when the event's toolkit (success) or provider (error) field matches the subscribed channel — siblings on other channels, and mismatched dispatches, are no-ops.

Duplicate / Superseded PR Handling

Summary by CodeRabbit

  • New Features

    • Reusable OAuth connection listener to handle OAuth success/error deep-link flows for Discord and Telegram.
    • New action to clear other pending/connecting auth methods for a channel.
  • Bug Fixes

    • Prevents multiple auth methods from remaining "connecting"; switching stops in-flight polling and clears sibling pending modes.
    • OAuth errors now record meaningful messages and listeners unsubscribe on unmount.
  • Tests

    • Added tests covering the OAuth listener and pending-clearing reducer behaviors.

Review Change Stack

@sanil-23 sanil-23 requested a review from a team May 19, 2026 21:50
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

Centralizes OAuth event bridging into Redux via a new useOAuthConnectionListener hook, adds clearOtherPendingForChannel to cancel sibling connecting auth modes, and integrates both into Discord and Telegram connect flows.

Changes

OAuth connection lifecycle and state management

Layer / File(s) Summary
clearOtherPendingForChannel reducer and tests
app/src/store/channelConnectionsSlice.ts, app/src/store/__tests__/channelConnectionsSlice.test.ts
Redux reducer iterates through auth modes for a channel and transitions sibling modes in connecting state to disconnected and clears lastError. Tests verify sibling cancellation, preservation of connected/error states, no-op on empty pending, and cross-channel isolation.
useOAuthConnectionListener hook and test suite
app/src/hooks/useOAuthConnectionListener.ts, app/src/hooks/__tests__/useOAuthConnectionListener.test.tsx
Hook listens to global oauth:success and oauth:error window events, filters by lowercased channel key, and dispatches Redux updates: success transitions to connected with capabilities; error records lastError with message or fallback. Tests cover success/error transitions, cross-channel event filtering, case-insensitive matching, custom capabilities persistence, and cleanup on unmount.
Discord component OAuth handling
app/src/components/channels/DiscordConfig.tsx
Discord component calls useOAuthConnectionListener to listen for OAuth events and aborts/clears managed-link state and dispatches clearOtherPendingForChannel during handleConnect to prevent sibling Discord auth methods from remaining in connecting state.
Telegram component OAuth handling
app/src/components/channels/TelegramConfig.tsx
Telegram component uses useOAuthConnectionListener for OAuth event bridging, aborts managed-DM polling when switching modes, expands handleConnect dependencies, and dispatches clearOtherPendingForChannel before initiating a new auth flow to ensure other Telegram auth modes are not left pinned.

Sequence Diagram

sequenceDiagram
  participant User
  participant BrowserWindow as Window
  participant Hook as useOAuthConnectionListener
  participant Redux as channelConnectionsSlice
  participant Component as DiscordConfig/TelegramConfig

  User->>BrowserWindow: complete OAuth redirect
  BrowserWindow->>Hook: emit oauth:success / oauth:error (detail.toolkit/provider, message)
  Hook->>Redux: dispatch upsertChannelConnection / setChannelConnectionStatus
  Component->>Redux: dispatch clearOtherPendingForChannel when starting new flow
  Redux->>Redux: update sibling auth modes (connecting -> disconnected) and set statuses
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

working

Poem

🐰 I hopped through events, ears on the wire,
Cleared stuck siblings so badges won't tire,
Hooks listen quietly to success and error,
Redux tidies states — no ghostly carrier,
Hooray — connections spring true and entire!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(channels): clear stale OAuth Connecting badges across auth modes (#2128)' accurately and specifically describes the primary change: preventing multiple OAuth connection methods from remaining pinned as Connecting simultaneously.
Linked Issues check ✅ Passed The PR fully addresses issue #2128's acceptance criteria: prevents stale Connecting badges, scopes pending state to provider/method, clears prior state when new OAuth flows start, shows failure state, covers Discord/Telegram, includes diagnostics, and meets 80%+ diff coverage with 12 test cases.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #2128: new useOAuthConnectionListener hook for OAuth event handling, clearOtherPendingForChannel reducer to cancel sibling auth modes, integration into DiscordConfig/TelegramConfig, and comprehensive test coverage. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@app/src/components/channels/DiscordConfig.tsx`:
- Around line 142-145: The Redux clearing call
dispatch(clearOtherPendingForChannel({ channel: 'discord', exceptAuthMode:
spec.mode })) can race with an already-running startLinkPolling loop that later
dispatches managed_dm back to connected/error; cancel that polling before
clearing state by invoking the cancellation/stop mechanism for startLinkPolling
(e.g., call stopLinkPolling or cancelLinkPollingTask associated with the discord
managed_dm poll) for the channel/auth mode(s) being cleared, ensure the poller
is awaited/confirmed stopped, then proceed to dispatch
clearOtherPendingForChannel and subsequent dispatches so the old poll cannot
revive the deprecated flow (referencing startLinkPolling, managed_dm, and
clearOtherPendingForChannel to locate the logic).

In `@app/src/components/channels/TelegramConfig.tsx`:
- Around line 167-170: When starting a new Telegram auth attempt (just before
the dispatch(clearOtherPendingForChannel({ channel: 'telegram', exceptAuthMode:
spec.mode }))) ensure any in-flight managed_dm polling is stopped so an old poll
cannot later dispatch connected/error and leak into state; add a call to the
existing poll-cleanup API (e.g. dispatch(cancelManagedDMPoll()) or call
stopManagedDMPoll()) or clear the stored poll subscription/timer from state
before dispatching clearOtherPendingForChannel, and ensure the cleanup
unsubscribes the poll's callbacks so managed_dm polling cannot update Redux
after cancellation.
🪄 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: e9d62e3e-635f-4c28-9c4e-79fae093b917

📥 Commits

Reviewing files that changed from the base of the PR and between 6a83409 and 2d93f7c.

📒 Files selected for processing (6)
  • app/src/components/channels/DiscordConfig.tsx
  • app/src/components/channels/TelegramConfig.tsx
  • app/src/hooks/__tests__/useOAuthConnectionListener.test.tsx
  • app/src/hooks/useOAuthConnectionListener.ts
  • app/src/store/__tests__/channelConnectionsSlice.test.ts
  • app/src/store/channelConnectionsSlice.ts

Comment thread app/src/components/channels/DiscordConfig.tsx
Comment thread app/src/components/channels/TelegramConfig.tsx
sanil-23 pushed a commit to sanil-23/openhuman that referenced this pull request May 19, 2026
…ngs (tinyhumansai#2128)

CodeRabbit on PR tinyhumansai#2256 flagged a real race in the new
clearOtherPendingForChannel flow: when the user switches auth modes,
the slice row for the prior method is reset to `disconnected`, but the
underlying managed-DM polling loop is still alive and can later
dispatch the cleared row back to `connected` or `error`, leaking the
old attempt into state.

Fix in both panels: cancel the in-flight poll *before* dispatching the
clear.

- DiscordConfig: unconditionally `pollAbort.current?.abort()` +
  `setLinkToken(null)` at the top of handleConnect. Safe in the same-
  mode case because startLinkPolling spawns a fresh controller.
- TelegramConfig: when starting a non-managed-dm mode, call
  `stopManagedDmPolling('telegram:managed_dm')` before the slice clear.
  (Same-mode case is already handled inside startManagedDmPolling.)
  Added `stopManagedDmPolling` to handleConnect's dep array per
  react-hooks/exhaustive-deps.

Co-Authored-By: Claude <noreply@anthropic.com>
@sanil-23
Copy link
Copy Markdown
Contributor Author

CI status — infra-only failure

After CodeRabbit's approving re-review on 5936c8d0 (Discord / Telegram), the two red checks are pure GitHub Actions infrastructure failures, unrelated to the diff (which is TypeScript-only — no Rust changes):

  • test / Rust Core Tests + Quality — failed at cargo metadata during Swatinem/rust-cache post-job cache save with ##[error]No space left on device : '/home/runner/actions-runner/cached/2.334.0/_diag/pages/...' (run). The container was cleaned up before the test step completed.
  • Rust Core Coverage (cargo-llvm-cov)cargo llvm-cov for core ran for 9m24s, then failed in the post-job cache step with System.IO.IOException: No space left on device : '/home/runner/actions-runner/cached/2.334.0/_diag/Worker_20260519-221722-utc.log' (run). Frontend Coverage and Rust Tauri Coverage on the same workflow both passed.
  • Coverage Gate (diff-cover ≥ 80%)skipped (waiting on Rust Core Coverage's lcov, which never uploaded because of the above).

The first CI run on 2d93f7c0 had all 22 required checks green — same diff up to the small race-condition fix, no Rust touched between runs. I can't gh run rerun --failed (no admin on the upstream repo). Could a maintainer re-run those two jobs? That should also un-skip the Coverage Gate.

CodeRabbit's APPROVED review of the latest commit covers the reviewer-comments gate.

sanil-23 and others added 2 commits May 20, 2026 08:09
…inyhumansai#2128)

OAuth connection badges could stay pinned at `Connecting` indefinitely:

- DiscordConfig had a per-component `oauth:success` effect but no error
  listener; TelegramConfig had neither, so completed and failed OAuth
  flows never transitioned its badge out of `connecting`.
- Starting a second OAuth method on the same channel left the first
  method's pending state in place, so two methods could appear active
  at once (the reproducer from the issue).

This change:

- Adds a shared `useOAuthConnectionListener` hook that bridges the
  global `oauth:success` / `oauth:error` deep-link CustomEvents from
  desktopDeepLinkListener.ts into the channelConnections slice,
  filtered case-insensitively by channel. DiscordConfig and
  TelegramConfig now both subscribe via this hook; future channels
  inherit correct pending-state transitions for free.
- Adds a `clearOtherPendingForChannel` reducer that cancels any
  sibling auth mode still mid-`connecting` (transitions it to
  `disconnected`, not `error`, so the UI doesn't surface a misleading
  failure) when a new connect flow starts in either panel.
- Covers the new reducer (4 cases) and hook (8 cases) with Vitest.

Co-Authored-By: Claude <noreply@anthropic.com>
…ngs (tinyhumansai#2128)

CodeRabbit on PR tinyhumansai#2256 flagged a real race in the new
clearOtherPendingForChannel flow: when the user switches auth modes,
the slice row for the prior method is reset to `disconnected`, but the
underlying managed-DM polling loop is still alive and can later
dispatch the cleared row back to `connected` or `error`, leaking the
old attempt into state.

Fix in both panels: cancel the in-flight poll *before* dispatching the
clear.

- DiscordConfig: unconditionally `pollAbort.current?.abort()` +
  `setLinkToken(null)` at the top of handleConnect. Safe in the same-
  mode case because startLinkPolling spawns a fresh controller.
- TelegramConfig: when starting a non-managed-dm mode, call
  `stopManagedDmPolling('telegram:managed_dm')` before the slice clear.
  (Same-mode case is already handled inside startManagedDmPolling.)
  Added `stopManagedDmPolling` to handleConnect's dep array per
  react-hooks/exhaustive-deps.

Co-Authored-By: Claude <noreply@anthropic.com>
@sanil-23 sanil-23 force-pushed the fix/2128-oauth-badge-pending-state branch from 5936c8d to a559bd8 Compare May 20, 2026 06:10
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 20, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@app/src/hooks/useOAuthConnectionListener.ts`:
- Line 67: The default array literal for the capabilitiesOnSuccess parameter is
causing the effect to re-run each render; extract that literal into a
module-level constant (e.g. DEFAULT_CAPABILITIES = ['read','write']) and use
that constant as the default value for the capabilitiesOnSuccess param in
useOAuthConnectionListener, so the reference is stable and the effect that
registers/unregisters the 'oauth:success' and 'oauth:error' listeners will not
resubscribe on every render.
🪄 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: c9431d6c-efef-4b12-9918-2de1b3ded009

📥 Commits

Reviewing files that changed from the base of the PR and between 5936c8d and a559bd8.

📒 Files selected for processing (6)
  • app/src/components/channels/DiscordConfig.tsx
  • app/src/components/channels/TelegramConfig.tsx
  • app/src/hooks/__tests__/useOAuthConnectionListener.test.tsx
  • app/src/hooks/useOAuthConnectionListener.ts
  • app/src/store/__tests__/channelConnectionsSlice.test.ts
  • app/src/store/channelConnectionsSlice.ts

Comment thread app/src/hooks/useOAuthConnectionListener.ts Outdated
…mansai#2128)

CodeRabbit on PR tinyhumansai#2256 flagged that the inline default array literal
`['read', 'write']` for `capabilitiesOnSuccess` creates a fresh
reference on every parent render. Since that prop is in the effect's
dep array, the global `oauth:success` / `oauth:error` listeners would
tear down and re-subscribe on every render of any panel using the
default — same class of bug as tinyhumansai#2177.

Hoist to a module-level `DEFAULT_OAUTH_CAPABILITIES` constant so the
identity is stable. No behaviour change at the call sites.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Nice work on this one — the shared useOAuthConnectionListener hook is a clean extraction, the clearOtherPendingForChannel reducer is scoped correctly (only touching connecting rows), and all three CodeRabbit findings have been addressed. Tests cover the important paths well.

File Area Summary
useOAuthConnectionListener.ts Frontend (new) Shared hook bridging oauth:success/oauth:error deep-link events into Redux
channelConnectionsSlice.ts Frontend New clearOtherPendingForChannel reducer
DiscordConfig.tsx Frontend Replaced inline OAuth effect with shared hook + sibling cancellation
TelegramConfig.tsx Frontend Added previously-missing OAuth listener + sibling cancellation
useOAuthConnectionListener.test.tsx Tests (new) 8 hook test cases
channelConnectionsSlice.test.ts Tests 4 reducer test cases

[major] PR-Issue alignment gap — GitHub/GitLab provider coverage

Issue #2128 acceptance criteria explicitly requires: "Provider coverage — The fix covers Discord, GitHub, GitLab, and other multi-method OAuth connection panels using the same state path." This PR only wires DiscordConfig and TelegramConfig. If GitHub/GitLab channel configs have OAuth auth modes going through the same channelConnections slice, they need useOAuthConnectionListener mounted too — otherwise the issue's acceptance criteria aren't met and those panels remain vulnerable to the same stale-badge bug.

The shared hook makes this straightforward to add. If you've verified those channels don't have the OAuth badge issue (e.g. they use a different state path or don't have multi-method OAuth), a note in the PR description explaining why they're excluded would close this out.


Everything else looks solid. The hook's channel-matching logic, the module-level DEFAULT_OAUTH_CAPABILITIES constant, the poll-abort ordering in both configs, and the disconnected (not error) transition for cancelled siblings are all good choices.

@sanil-23
Copy link
Copy Markdown
Contributor Author

@graycyrus thanks for the read. You called it correctly — and the answer is the second option you offered: GitHub / GitLab don't have channel-config panels in this codebase today, so there's nothing to wire the new hook into for those providers.

I've added a Provider coverage section to the PR description with the full evidence. Short version:

  • find app/src -name "*Config.tsx" returns three channel configs total: DiscordConfig.tsx, TelegramConfig.tsx, WebChannelConfig.tsx (the last is not OAuth-driven). No GitHubConfig.tsx / GitLabConfig.tsx anywhere.
  • GitHub OAuth does exist elsewhere — BootCheckGate.tsx (app-level sign-in), InstallSkillDialog.tsx (skill OAuth), composio/TriggerToggles.tsx (Composio integration) — but each lives on a different Redux/state slice (deepLinkAuth, skills-domain, Composio-integration) and doesn't share the channelConnections code path this PR's hook is bound to.
  • The shared useOAuthConnectionListener is the right shape for any future GitHubConfig.tsx / GitLabConfig.tsx if they get added — wiring becomes a single import.

If the stale-Connecting symptom turns out to surface in the app-level sign-in or skill / Composio OAuth flows (with their own stuck-state mechanics on those other slices), I'd treat each as a separate fix against the relevant slice rather than expanding this PR.

Let me know if the body update lands the concern or if you'd rather see the missing-panel detection asserted in a regression test as well.

@senamakel senamakel merged commit ec74c73 into tinyhumansai:main May 20, 2026
30 checks passed
@senamakel
Copy link
Copy Markdown
Member

huge thanks @sanil-23, this one's a gem 🙌 centralising the oauth badge transitions behind that new listener hook is such a clean fix, no more stale "connecting" badges hanging around ✨ really appreciate you back here again 💚

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Channel selector hides channel error state and falls back to Disconnected OAuth connection badges get stuck in Connecting

4 participants