## 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`](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 reducer** — `clearOtherPendingForChannel({ 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 wiring** — `DiscordConfig` 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 - [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) — 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. - [x] **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). - [x] Coverage matrix updated — `N/A: behaviour-only fix on existing surfaces (channel connection pending state)`. - [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — `N/A: no feature ID changes`. - [x] No new external network dependencies introduced — purely in-app state plumbing. - [x] 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)`. - [x] 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 change** — `channelConnections` 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 - Closes: #2128 - Follow-up PR(s)/TODOs: none ## 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 - [x] `pnpm --filter openhuman-app format:check` — `All matched files use Prettier code style!` on the 6 changed files - [x] `pnpm typecheck` — clean (`tsc --noEmit`) - [x] 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 - [x] Rust fmt/check (if changed): `N/A: no Rust changes` - [x] 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 ripgrep` — `rg` 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 - Duplicate PR(s): none found. #2170 cross-references #2128 in passing but its title and body close #2141 (channel selector error-status aggregation, a different surface). - Canonical PR: this one. - Resolution: N/A. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2256?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: sanil-23 <sanil@alphahuman.xyz> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
OpenHuman
OpenHuman is your Personal AI super intelligence. Private, Simple and extremely powerful.
Discord • Reddit • X/Twitter • Docs • Follow @senamakel (Creator)
🇺🇸 English | 🇨🇳 简体中文 | 🇯🇵 日本語 | 🇰🇷 한국어 | 🇩🇪 Deutsch
Early Beta: Under active development. Expect rough edges.
To install or get started, either download from the website over at tinyhumans.ai/openhuman or run
# Download DMG, EXEs over at https://tinyhumans.ai/openhuman or run in from your terminal
# For macOS or Linux x64
curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash
# For Windows
irm https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.ps1 | iex
What is OpenHuman?
OpenHuman is an open-source agentic assistant designed to integrate with you in your daily life. Each bullet links to the deeper writeup in the docs.
-
Simple, UI-first & Human A clean desktop experience and short onboarding paths take you from install to a working agent in a few clicks — no config-first setup, no terminal required. The agent has a face: a desktop mascot that speaks, reacts to its surroundings, joins your Google Meets as a real participant, remembers you across weeks, and keeps thinking in the background even when you've stopped typing.
-
118+ third-party integrations with auto-fetch: plug into Gmail, Notion, GitHub, Slack, Stripe, Calendar, Drive, Linear, Jira and the rest of your stack with one-click OAuth. Every connection is exposed to the agent as a typed tool, and every twenty minutes the core walks each active connection and pulls fresh data into the memory tree. No prompts, no polling loops you have to write, so the agent already has tomorrow's context this morning.
-
Memory Tree + Obsidian Wiki: a local-first knowledge base built from your data and your activity. Everything you connect is canonicalized into ≤3k-token Markdown chunks, scored, and folded into hierarchical summary trees stored in SQLite on your machine. The same chunks land as
.mdfiles in an Obsidian-compatible vault you can open, browse and edit, inspired by Karpathy's obsidian-wiki workflow. -
Batteries included: web search, a web-fetch scraper, a full coder toolset (filesystem, git, lint, test, grep), and native voice (STT in, ElevenLabs TTS out, mascot lip-sync, live Google Meet agent) are wired in by default. Model routing sends each task to the right LLM (reasoning, fast, or vision) under one subscription. No "install a plugin to read files" friction. Optional local AI via Ollama for on-device workloads.
-
Smart token compression (TokenJuice): every tool call, scrape result, email body, and search payload is run through a token compression layer before it touches any LLM Model. HTML is converted to Markdown, long URLs are shortened, and verbose tool output is deduped and summarized via a configurable rule overlay etc... CJK, emoji, and other multi-byte text are preserved grapheme-by-grapheme — never stripped. You get the same information but at a fraction of the tokens. Reducing cost & latency by up to 80%.
-
Messaging channels and privacy & security: inbound/outbound across the channels you already use, with workflow data that stays on device, encrypted locally, treated as yours.
Contributing from source
New contributor? Start with CONTRIBUTING.md for the fork/PR workflow and local validation commands, or use the copy-paste AI-agent prompt in CONTRIBUTING-BEGINNERS.md. The short path is:
- Install Git, Node.js 24+, pnpm 10.10.0, Rust 1.93.0 (
rustfmt+clippy), CMake, Ninja, ripgrep, and the platform desktop build prerequisites. - Fork and clone the repo, then run
git submodule update --init --recursivebeforepnpm installso the vendored Tauri/CEF sources are present. - Use
pnpm devfor web-only UI work,pnpm --filter openhuman-app dev:appfor the desktop shell, and focused checks such aspnpm typecheck,pnpm format:check, andcargo check -p openhuman --libbefore opening a PR.
Deeper docs: Architecture · Getting Set Up · Cloud Deploy.
Context in minutes, not weeks
OpenHuman is the first agent harness that gets to know you in minutes. Inspired by Karpathy's LLM Knowledgebase. Most agents start cold. Hermes learns by watching you work; OpenClaw waits for plugins to ferry context in. Either way, you spend days or weeks before the agent knows enough about your stack to be genuinely useful.
OpenHuman summarizes and compresses all your documents, emails & chats; and creates a memory graph that lets your agent remember everything about you.
OpenHuman skips the wait. Connect your accounts, let auto-fetch pull data locally on a 20-minute loop, and then have Memory Trees compress everything into Markdown files stored intelligently in a Karpathy-style Obsidian wiki.
In just one sync pass, the agent has full (compressed) context of your inbox, your calendar, your repos, your docs, your messages. No training period. No "give it a few weeks.". It becomes you, controlled by you.
Already self-host agentmemory across other coding agents? OpenHuman ships an optional Memory backend that proxies to it — set memory.backend = "agentmemory" in config.toml and the same durable store powers OpenHuman alongside Claude Code, Cursor, Codex, and OpenCode. See the agentmemory backend page for setup.
OpenHuman vs Other Agent Harnesses
High-level comparison (products evolve, so verify against each vendor). OpenHuman is built to minimize vendor sprawl, keep workflow knowledge on-device, and give the agent a persistent memory of your data, not only chat.
| Claude Cowork | OpenClaw | Hermes Agent | OpenHuman | |
|---|---|---|---|---|
| Open-source | 🚫 Proprietary | ✅ MIT | ✅ MIT | ✅ GNU |
| Simple to start | ✅ Desktop + CLI | ⚠️ Terminal-first | ⚠️ Terminal-first | ✅ Clean UI, minutes |
| Cost | ⚠️ Sub + add-ons | ⚠️ BYO models | ⚠️ BYO models | ✅ One sub + TokenJuice |
| Memory | ✅ Chat-scoped | ⚠️ Plugin-reliant | ✅ Self-learning | 🚀 Memory Tree + Obsidian vault, optional agentmemory backend |
| Integrations | ⚠️ Few connectors | ⚠️ BYO | ⚠️ BYO | 🚀 118+ via OAuth |
| Auto-fetch | 🚫 None | 🚫 None | 🚫 None | ✅ 20-min sync into memory |
| API sprawl | 🚫 Extra keys | 🚫 BYOK | 🚫 Multi-vendor | ✅ One account |
| Model routing | 🚫 Single model | ⚠️ Manual | ⚠️ Manual | ✅ Built-in |
| Native tools | ✅ Code-only | ✅ Code-only | ✅ Code-only | ✅ Code + search + scraper + voice |
Star us on GitHub
Building toward AGI and artificial consciousness? Star the repo and help others find the path.
Contributors Hall of Fame
Show some love and end up in the hall of fame. Contributors get free merch and special access to our Discord.

