Skip to content

[fix] Connect-flow feedback: actionable errors, correct auth mode, working decline - #5909

Merged
mmabrouk merged 6 commits into
release/v0.112.0from
fix/connect-flow-feedback
Aug 10, 2026
Merged

[fix] Connect-flow feedback: actionable errors, correct auth mode, working decline#5909
mmabrouk merged 6 commits into
release/v0.112.0from
fix/connect-flow-feedback

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Context

Mahmoud hit a dead end trying to connect a Telegram agent to Composio (live sessions e8c3b72a-0fb0-4895-a77d-3f073672da8a and 9d4e0324-344c-42f0-ab72-a7afe0246b72 on the 8180 dev stack, session_interactions rows stuck pending). Four bugs compounded into that dead end; a fifth (found mid-fix, on the coordinator's steer) is the actual root cause that made the flow unusable for telegram in the first place. This PR fixes four of the five. The fifth, the dock's stopped gate silently hiding a re-parked interaction, is being isolated by a separate instrumented capture and will land as its own small follow-up (see "Known remaining issue" below).

Changes

1. Backend 500 -> actionable 422. Composio has no managed OAuth config for some toolkits (telegram is one; it only supports use_custom_auth). POST /api/tools/connections/ raised AdapterError from the gateway connections domain, a different class from the one @handle_adapter_exceptions catches (core/tools/exceptions), so it fell through to the generic @intercept_exceptions handler and returned a bare 500. create_connection now catches the gateway AdapterError directly and returns 422 with a message telling the user the toolkit needs custom OAuth credentials in this environment.

2. The real root cause: wrong auth mode, not a Composio outage. While reproducing bug 1 live, the coordinator caught that the telegram POST always sent auth_scheme: "oauth", and traced it to useConnectFlow's mode blindly trusting the agent's input.mode hint (which defaults to oauth). Telegram's real scheme is a bot token (api_key-style), and the adapter already supports use_custom_auth for it (adapter.py:162-175) - the flow just never asked for it. Live proof this works: POST /tools/connections/ for telegram with auth_scheme: "api_key" on a minted 8180 account returns 200 with a redirect_url (https://connect.composio.dev/link/...), identical in shape to the OAuth path - Composio's own hosted page collects the bot token, so no new widget UI is needed.

I first checked whether the settings page's ConnectModal already picks the right mode (it looked like it should) - it doesn't. Its resolveAvailableModes reads integration.auth_schemes, which the backend built only from Composio's composio_managed_auth_schemes field. For telegram that field is empty (confirmed live: GET .../tools/catalog/providers/composio/integrations/telegram returns no auth_schemes at all), so resolveAvailableModes falls through its own length === 0 -> oauth default. Same latent bug, just unhit because most toolkits do have managed oauth. Fixed at the shared source: _parse_integration_detail now falls back to auth_config_details (the same field the connections adapter reads) when the managed list is empty, so both the settings modal and the agent widget see the toolkit's real scheme.

useConnectFlow now cross-checks the agent's mode hint against the toolkit's real auth_schemes (fetched via useToolIntegrationDetail) and overrides it when the toolkit doesn't actually support the hinted mode.

3. Silent failure on the widget. When the create POST failed, useConnectFlow settled it through the exact same path as decline/cancel/timeout (finish() -> a ConnectOutput, not {errorText}). ConnectToolWidget's render order checks meta.settled/outcome before phase === "error", so a real failure always fell through to a generic "Connection not completed" chip - Retry looked completely dead, no error anywhere. The failure reason is now carried through settle()'s output and rendered verbatim unless it's one of the three expected non-error terminal states (declined/cancelled/timeout). Error messages prefer the backend's own 4xx detail over Fern's default Error.message, which bundles a multi-line status/body dump not fit to show a user.

4. "Not now" fired zero network requests. Reproduced live: clicking "Not now" on a parked connect request produced no new /services/agent/v0/invoke call, and the transcript visibly reverted to an earlier turn. Root cause: useSessionRecordsWatch's SSE relay can tick and adopt a fresh server transcript while the run is idle (busy=false - the resume hasn't been dispatched yet) but a local addToolOutput settle is still waiting for sendAutomaticallyWhen to actually fire it. liveGateInteractionRef is written synchronously the instant the settle happens and only cleared once dispatch lands, so it's non-null for exactly that gap. A relay tick landing in that window adopted a transcript that predated the settle, silently discarding it before the resume could ever be sent. useSessionHydration now takes that same ref (pendingResumeRef, no new signal invented) and skips the relay's adoption while it's set.

Known remaining issue (not in this PR)

AgentConversation.tsx:351 gates the connect dock on busy || stopped ? null : .... stopped only clears on a new submit/regenerate, but the server-side run can continue past a client Stop and park a NEW interaction, which the latched stopped then hides forever. A separate instrumented capture is isolating this (two competing theories for why the widget never rendered in one observed case); it'll land as its own small follow-up once the evidence picks the winner.

Tests / notes

  • Backend: ruff format + ruff check clean. New unit tests: api/oss/tests/pytest/unit/tools/test_create_connection_errors.py (3 tests, router-level, exercises the actual ToolsRouter.create_connection handler with a faked service raising the real exception), api/oss/tests/pytest/unit/tools/test_catalog_auth_schemes_fallback.py (4 tests, the auth_config_details fallback and a regression guard that managed-auth toolkits are untouched). All pass.
  • Frontend: tsc --noEmit clean, pnpm turbo run lint --filter=@agenta/oss clean (no new warnings). New unit tests: useConnectFlow.test.ts (7 tests, resolveConnectMode + extractConnectErrorMessage), useSessionHydration.test.ts (4 tests, the new shouldSkipRecordsRefresh guard). Full AgentChatSlice vitest suite (162 tests, 21 files) passes with no regressions.
  • Live-verified on the 8180 dev stack (minted ephemeral accounts, real Composio calls), before this branch's fix:
    • Bug 1 before-state: POST /tools/connections/ for telegram -> 500, traceback matching adapter.py:188 / router.py:773 exactly.
    • Bug 5 before-state: GET .../catalog/providers/composio/integrations/telegram -> no auth_schemes; POST /tools/connections/ with auth_scheme: "api_key" -> 200 + redirect_url (proves the fix's target shape works today, api key just isn't being requested).
    • Bug 4 before-state / bug 3 reproduction: drove a real agent conversation through the elicitation form to the connect ask, clicked "Not now", confirmed zero new /services/agent/v0/invoke requests and the transcript reverting to an earlier turn (matches the session_interactions rows for Mahmoud's two sessions, still pending with no updated_at).
    • This worktree's actual code changes could not be re-verified live: the 8180 stack hot-reloads the main checkout, not this branch. Verified by unit test + code trace instead, per the repo's verification convention for this situation.

What to QA

  • Request a telegram connection through an agent (or any toolkit with no Composio-managed OAuth). The widget should ask for api_key-mode custom auth and open a redirect popup, not 404.
  • Force a connect failure (e.g. a toolkit slug Composio doesn't recognize) and confirm the widget shows the real error text, not a silent "Connection not completed".
  • Click "Not now" on a parked connect request. Confirm a /services/agent/v0/invoke request fires and the agent's next turn acknowledges the decline.
  • Regression: a normal successful OAuth connect (e.g. github) still opens its popup and settles "connected" as before.

POST /api/tools/connections/ returned a bare, unactionable 500 when Composio has
no managed OAuth auth config for a toolkit (e.g. telegram) — the adapter raised
AdapterError (core/gateway/connections/exceptions), a different class from the
one @handle_adapter_exceptions catches (core/tools/exceptions), so it fell
through to the generic @intercept_exceptions handler.

create_connection now catches the gateway AdapterError directly and returns a
422 with a message telling the user this toolkit needs custom OAuth
credentials in this environment; any other AdapterError from create_connection
also gets the upstream detail instead of a generic 500.

Before-state reproduced live on the 8180 dev stack (POST .../tools/connections/
for telegram -> 500, traceback matching adapter.py:188/router.py:773 exactly).
…OAuth

integration.auth_schemes came back None for toolkits like telegram that have
no Composio-managed auth config at all (composio_managed_auth_schemes is
empty), even though they ARE connectable via use_custom_auth. Every caller
that picks a connect mode from this field — the settings ConnectModal's
resolveAvailableModes, and the agent connect widget's mode resolver added in
this PR — defaults to "oauth" when it sees no schemes, which Composio then
404s on for these toolkits.

Fall back to auth_config_details (the same field the connections adapter
already reads to pick use_custom_auth's authScheme) when the managed list is
empty, mapping any non-oauth/non-no_auth mode to api_key — mirrors the
connections adapter's own "oauth" not in mode.lower() heuristic exactly.

Verified live on the 8180 dev stack: GET .../tools/catalog/providers/composio/
integrations/telegram currently returns no auth_schemes at all (before-state).
POST .../tools/connections/ with data.auth_scheme=api_key for telegram
returns 200 with a redirect_url identical in shape to the OAuth path —
Composio's own hosted page collects the credential, so no new widget UI is
needed for this mode, only correct mode selection (see the next commit).
…al auth mode

Two bugs in the request_connection widget, both from a failed create POST
(e.g. bug 1's telegram 500/422):

1. Silent failure. useConnectFlow settled a create failure through the same
   path as decline/cancel/timeout (finish() -> ConnectOutput, not
   {errorText}), so ConnectToolWidget's render order — which checks
   meta.settled/outcome before phase==="error" — always fell through to a
   generic "Connection not completed" chip with no indication anything had
   gone wrong. From the user's seat, Retry looked completely dead. The reason
   is now carried through settle()'s output and rendered verbatim unless it's
   one of the three expected non-error terminal states (declined/cancelled/
   timeout).

2. Wrong auth mode. mode was read straight from the agent's input.mode hint,
   defaulting to "oauth" whenever the agent didn't say "api_key" — but the
   agent has no way to know Composio's supported schemes either. useConnectFlow
   now cross-checks the hint against the toolkit's real auth_schemes (fetched
   via useToolIntegrationDetail, mirroring the settings ConnectModal's
   resolveAvailableModes) and overrides it when the toolkit doesn't actually
   support it.

Error messages prefer the backend's own 4xx detail (extractConnectErrorMessage)
over Fern's default Error.message, which bundles a multi-line status/body dump
not fit to show a user.

Verified: 7 new vitest unit tests for the two pure helpers (resolveConnectMode,
extractConnectErrorMessage); tsc --noEmit clean; pnpm turbo run lint clean.
… decline

Clicking "Not now" on a parked connect request fired ZERO network requests,
the session_interactions row stayed pending forever, and the agent turn never
resumed. Reproduced live on the 8180 dev stack: no new
/services/agent/v0/invoke request after the click, and the transcript
visibly reverted to an earlier turn.

Root cause: useSessionRecordsWatch's SSE relay can tick and call
refreshFromRecords while the run is idle (busy=false — the resume hasn't been
dispatched yet) but a local addToolOutput settle (decline, or any client-tool
settle) is still waiting for sendAutomaticallyWhen to actually dispatch it.
liveGateInteractionRef is written synchronously the moment the settle fires
and only cleared once dispatch lands, so it is non-null for exactly that gap.
A relay tick landing in that window adopted a server transcript that predates
the settle, silently discarding it before the resume could ever be sent.

useSessionHydration now takes pendingResumeRef (wired to the existing
liveGateInteractionRef in useAgentChatSession — no new signal invented) and
skips the records-changed relay's adoption while it is set, via the new
shouldSkipRecordsRefresh guard alongside the existing busy check. The poll and
one-time SWR-on-open paths don't need the guard: they don't fire mid-
conversation from a local settle the way the relay does.

Verified: 4 new unit tests for shouldSkipRecordsRefresh; the live repro above
pinned the before-state (this exact worktree's fix could not be re-verified
live, since the 8180 stack hot-reloads the main checkout, not this branch —
see the AGENTS.md verification note); full AgentChatSlice vitest suite (162
tests) and tsc --noEmit stay clean.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. backend bug report Something isn't working frontend tests labels Aug 10, 2026
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Error Error Aug 10, 2026 3:29pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: d7343c02-8be8-4f6b-a85a-2146016fa758

📥 Commits

Reviewing files that changed from the base of the PR and between d2a6d55 and a41eceb.

📒 Files selected for processing (5)
  • web/oss/src/components/AgentChatSlice/components/InteractionDock.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/ConnectToolWidget.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.test.ts
  • web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.ts
  • web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.ts
  • web/oss/src/components/AgentChatSlice/components/clientTools/ConnectToolWidget.tsx

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Connection setup failures now provide actionable details instead of generic server errors.
    • Connection modes are detected more reliably when authentication information is incomplete.
    • Unexpected connection failures display specific error messages, while declined, cancelled, or timed-out attempts retain clear status messaging.
    • Connect and Retry actions are temporarily disabled while connection details are being resolved.
    • Prevented session refreshes from overwriting active tool approvals, streaming responses, or pending connection results.

Walkthrough

The PR adds Composio auth-scheme fallback, HTTP 422 handling for connection adapter failures, clearer client connection errors, and safeguards that prevent session refreshes from overwriting active client-tool interactions.

Changes

Connection flow

Layer / File(s) Summary
Catalog authentication fallback
api/oss/src/core/gateway/catalog/providers/composio/adapter.py, api/oss/tests/pytest/unit/tools/test_catalog_auth_schemes_fallback.py
Composio parsing falls back to auth_config_details, maps supported modes to OAuth or API key, excludes NO_AUTH, and preserves managed schemes when present.
Connection API error handling
api/oss/src/apis/fastapi/tools/router.py, api/oss/tests/pytest/unit/tools/test_create_connection_errors.py
Connection adapter failures return HTTP 422 responses with custom OAuth guidance or adapter details. Tests cover failures and successful creation.
Client connection resolution and results
web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.ts, web/oss/src/components/AgentChatSlice/components/clientTools/ConnectToolWidget.tsx, web/oss/src/components/AgentChatSlice/components/InteractionDock.tsx, web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.test.ts
The client selects a supported auth mode, blocks actions during resolution, extracts 4xx error details, retains terminal reasons, and renders unexpected failures with error styling.

Session hydration safeguards

Layer / File(s) Summary
Record refresh guard
web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.ts
Record refreshes are skipped during local streaming or while a client-tool resume is pending. The states are checked again after fetching records.
Pending resume wiring and validation
web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts, web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.test.ts
The live interaction ref is passed into hydration. Tests cover active streaming, pending resume, and idle states.

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

Sequence Diagram(s)

sequenceDiagram
  participant ConnectToolWidget
  participant useConnectFlow
  participant ToolsConnectionsAPI
  participant ComposioAdapter
  ConnectToolWidget->>useConnectFlow: request connection
  useConnectFlow->>ToolsConnectionsAPI: create connection with resolved auth mode
  ToolsConnectionsAPI->>ComposioAdapter: create connection
  ComposioAdapter-->>ToolsConnectionsAPI: result or adapter error detail
  ToolsConnectionsAPI-->>useConnectFlow: connection result or HTTP 422 detail
  useConnectFlow-->>ConnectToolWidget: terminal reason or failure message
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main connect-flow fixes, including actionable errors, correct authentication mode, and decline handling.
Description check ✅ Passed The description directly explains the connect-flow bugs, fixes, tests, verification, and known follow-up issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/connect-flow-feedback

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.

@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: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 13b9d158-7c91-4cd5-914b-d8194d78e91d

📥 Commits

Reviewing files that changed from the base of the PR and between e6e999b and d2a6d55.

📒 Files selected for processing (10)
  • api/oss/src/apis/fastapi/tools/router.py
  • api/oss/src/core/gateway/catalog/providers/composio/adapter.py
  • api/oss/tests/pytest/unit/tools/test_catalog_auth_schemes_fallback.py
  • api/oss/tests/pytest/unit/tools/test_create_connection_errors.py
  • web/oss/src/components/AgentChatSlice/components/clientTools/ConnectToolWidget.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.test.ts
  • web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.test.ts
  • web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.ts

Comment thread web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.ts Outdated
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-08-10T16:51:18.407Z

…nown

CodeRabbit finding on #5909: during the initial catalog request,
`integrationDetail` is undefined and `resolveConnectMode` retains the raw
agent hint — a user who clicks Connect in that window still sends "oauth" for
a toolkit like telegram that only supports api_key, 404ing exactly like
before bug 1's fix. `resolveConnectMode`'s "keep the hint" fallback is correct
for the RENDER (it must return *something*), but a click landing in that same
window must not be allowed to act on it.

New `isConnectModeResolving` (pure, unit-tested): true only while there's a
real integration key AND its catalog lookup is genuinely in flight AND a
bounded 8s wait hasn't elapsed. Both `hasIntegrationKey: false` (a malformed
call — a disabled TanStack Query reports `isLoading: true` forever, which
must not permanently disable Connect) and `timedOut: true` (a dead network
must not either) force it false — the gate cannot latch shut. Connect/Retry
are disabled in the dock and the inline widget for this window, and
`runConnect` itself refuses to fire as defense-in-depth against a click that
raced the disabled state.

4 new tests pin the four cases (blocks / unblocks-on-resolve /
unblocks-for-no-key / unblocks-on-timeout); the existing
"keeps the hint while loading" test on `resolveConnectMode` is re-labeled to
make clear it's describing the RENDER fallback, not a guarantee a click can
act on.
…olves

CodeRabbit finding on #5909: `shouldSkipRecordsRefresh` was only checked at
the TOP of `refreshFromRecords`, before `loadSessionMessages` (a real network
round trip) even starts. If a client-tool settle (e.g. a connect decline)
arrives WHILE that fetch is in flight, the entry check already passed and
never re-runs — so the `.then` still adopts the (now stale) transcript it
fetched before the settle happened, clobbering it exactly the way the
previous commit in this family (the `pendingResumeRef` guard itself) was
meant to prevent.

`refreshFromRecords` now re-checks `shouldSkipRecordsRefresh` a second time,
immediately before `adoptServerTranscriptRef.current(...)` inside the
`.then`, using the same live refs — guarded at both the entry point and the
adoption point, closing the gap between "checked" and "the fetch that was in
flight when a settle landed."
@mmabrouk
mmabrouk merged commit 96e42a9 into release/v0.112.0 Aug 10, 2026
60 of 64 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend bug report Something isn't working frontend size:L This PR changes 100-499 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant