Skip to content

Link direct-mode Composio connections with the v3 body - #102

Merged
M3gA-Mind merged 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/composio-v3-direct-authorize
Nov 11, 2026
Merged

M3gA-Mind merged 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/composio-v3-direct-authorize

Conversation

@M3gA-Mind

Copy link
Copy Markdown
Contributor

Summary

In direct mode, where the user brings their own Composio API key, connecting any integration fails with HTTP 400: Validation error while processing request.

DirectRoute::authorize posted the proxy-route body to v3 /connected_accounts/link: {toolkit, extra params, scopes}, plus an inserted entity_id. Composio's v3 reference requires auth_config_id and user_id there (v3 API reference, migration guide).

The direct route now:

  • resolves the toolkit to an auth config with GET /auth_configs?toolkit_slug=…, reusing an enabled config for that toolkit and preferring a Composio-managed one;
  • creates a Composio-managed config with POST /auth_configs ({toolkit: {slug}, auth_config: {type: "use_composio_managed_auth"}}) when the toolkit has none;
  • posts only {auth_config_id, user_id} to /connected_accounts/link;
  • maps a Composio 400/422 to Error::Authorize with Composio's detail, so a host can show why the connection could not start.

Related issue

Closes #101

API or behavior changes

No public API change. Direct-route behaviour changes:

  • Authorize may now make one or two extra requests, the auth-config lookup and, the first time for a toolkit, its creation.
  • A rejected request surfaces as Error::Authorize instead of Error::Transport.
  • Extra params and the toolkit-required OAuth scopes are no longer sent with the link on the direct route. The v3 link documents no field for them, so they're logged at debug instead.
  • The proxy route is unchanged.

Not verified against a live Composio account: the fields used are the ones Composio's v3 reference documents for GET/POST /auth_configs and /connected_accounts/link. One real direct-mode connect with an own key would confirm the flow end to end before release. It would also show whether toolkits that need extra scopes (Gmail read) need them set on the auth config.

Validation

Run on our Linux CI runner, on this exact tree, checked out from main d424749:

  • cargo fmt --all (applied; the commit is formatted)
  • cargo clippy --locked --all-targets --all-features -- -D warnings: pass
  • cargo build --locked --all-targets --all-features: pass
  • cargo test --locked --all-features: 738 passed, 0 failed

Tests

  • The test transport now rejects a link body without auth_config_id/user_id, or with toolkit, using Composio's 400. A dedicated test proves the old {toolkit, entity_id} body is rejected, so the other tests prove the new body rather than a permissive mock.
  • New tests:
    • the link sends exactly {auth_config_id, user_id} and reuses an existing config;
    • a Composio-managed config is created when only disabled or other-toolkit configs exist;
    • managed is preferred over custom;
    • a create response with no id is a decode error;
    • a 400 becomes Error::Authorize with the detail.
  • The existing authorize tests now go through the auth-config lookup.
  • Not covered: Composio's paging of GET /auth_configs beyond one page. It reuses the existing list_pages helper, which has its own tests.

Documentation

Not needed. The behaviour is documented on DirectRoute::authorize and the new helper.

Checklist

  • The change is focused on one logical change
  • No new #[allow(...)], #[ignore], or relaxed lints
  • No secrets, tokens, or .env contents in the diff or the description

On the direct route (the user's own Composio key), every authorize
failed with "HTTP 400: Validation error while processing request".
DirectRoute::authorize posted the proxy-route body, {toolkit, extra
params, scopes} plus entity_id, to v3 /connected_accounts/link. v3
requires auth_config_id and user_id there.

The direct route now:
- resolves the toolkit to an auth config with GET /auth_configs
  ?toolkit_slug=, reusing an enabled config and preferring a
  Composio-managed one;
- creates a Composio-managed config with POST /auth_configs when the
  toolkit has none;
- posts only {auth_config_id, user_id} to /connected_accounts/link;
- turns a Composio 400/422 into Error::Authorize carrying Composio's
  detail, so a host can show why the connection could not start.

The proxy route is unchanged. On the direct route, extra params and
toolkit-required OAuth scopes are no longer sent with the link; they
are logged at debug.

The test transport now rejects a link body without auth_config_id and
user_id, or with toolkit, the way v3 does, so the tests prove the body
rather than a permissive mock.

Closes tinyhumansai#101
@tinysweeper

tinysweeper Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Reviewed head: 138fdc8b984ff40941fc325d572346018aa8b9bf

Findings

No active actionable findings.

Advisory notes

  • medium: Exercise the link POST refusal, not the config lookup (crates/tinyconnectors/src/client/route/direct_tests.rs:415)
    Problem: `FakeTransport::failing` puts the message in `fail`, and `answer` returns `Err` for **every** path, so `route(transport).authorize("gmail", …)` never gets as far as `/connected_acc
    Suggested fix:
    No concrete fix was supplied.
  • medium: Pin the pass-through branch of refused_request (crates/tinyconnectors/src/client/route/direct.rs:428)
    Problem: The doc asserts "Every other failure passes through unchanged", but the only test that feeds an error through `refused_request` uses an `HTTP 400` message, so the `other => other`
    Suggested fix:
    No concrete fix was supplied.

Verdict: Ready for maintainer review · Priority: medium
Coverage: Reviewed 2 of 2 files.

Diagnostics and supporting details

Full summary

Pull request purpose (title): Link direct-mode Composio connections with the v3 body. Title: Link direct-mode Composio connections with the v3 body. The pull request fixes direct-mode Composio connections, which failed with `HTTP 400: Validation error while processing request` because `DirectRoute::authorize` sent a proxy-shaped body to v3 `/connected_accounts/link`. The direct route now resolves the toolkit to an auth config (reusing an enabled one, preferring a Composio-managed config, or creating one), posts only `{auth_config_id, user_id}`, and maps Composio 400/422 to `Error::Authorize` with Composio's detail. No push delta is available: the review state has the prior reviewed head but no trusted base-to-head comparison.

Review snapshot

Change surface Files Review signal Count
Production 1 Active findings 0
Tests 1 Noted findings 2
Documentation 0 Resolved findings 0
Configuration 0 Pending checks/questions 0

Completeness: Complete
Test assessment: Test coverage is assessed from changed tests and lane evidence; execution is not claimed without trusted check data.

Features

  • Added — Auth-config resolution for direct-mode links: Direct-mode authorize now resolves a toolkit to an auth config before linking, reusing an enabled config (preferring a Composio-managed one) or creating a managed config when none is usable, so the v3 link receives a valid `auth_config_id`. This adds one or two requests to the authorize flow. (crates/tinyconnectors/src/client/route/direct.rs impl DirectRoute {, crates/tinyconnectors/src/client/route/direct.rs impl Route for DirectRoute {)
  • Modified — v3 link body for direct mode: The link POST now sends only `{auth_config_id, user_id}` instead of the proxy-shaped `{toolkit, extra params, scopes, entity_id}` body, matching v3's required fields; extra params and scopes are no longer sent and are logged at debug. The proxy route is unchanged. (crates/tinyconnectors/src/client/route/direct.rs impl Route for DirectRoute {)
  • Added — Refused-request error mapping: A Composio 400/422 on the auth-config lookup, create, or link call surfaces as `Error::Authorize` with Composio's detail so a host can show why the connection could not start; other failures pass through unchanged. (crates/tinyconnectors/src/client/route/direct.rs impl Route for DirectRoute {)
  • Modified — Stricter fake transport for link bodies: The test transport now rejects a link body lacking `auth_config_id`/`user_id` or containing `toolkit` with Composio's 400, and supports prefix-routed replies and a POST log, so the tests exercise the new body rather than a permissive mock. (crates/tinyconnectors/src/client/route/direct_tests.rs struct FakeTransport {, crates/tinyconnectors/src/client/route/direct_tests.rs impl Transport for FakeTransport {, crates/tinyconnectors/src/client/route/direct_tests.rs impl FakeTransport {)

Tests

  • unit — Asserts the link sends exactly `{auth_config_id: "ac_gmail", user_id: "entity-1"}` and that an existing config is reused with no create POST.: Covers the new link body and config reuse; renamed to reflect the auth-config lookup. (crates/tinyconnectors/src/client/route/direct_tests.rs async fn authorize_reads_the_v3_redirect_url_and_stamps_the_entity() {)
  • unit — Asserts a managed auth config is created when only a disabled gmail config and another toolkit's config exist, and that the created id is used in the link.: Covers the create path and its exact request body. (crates/tinyconnectors/src/client/route/direct_tests.rs impl FakeTransport {)
  • unit — Asserts a Composio-managed config is chosen over a custom enabled one.: Covers the managed-config preference. (crates/tinyconnectors/src/client/route/direct_tests.rs impl FakeTransport {)
  • unit — Asserts a missing id in the response yields `Error::Decode`.: Covers the no-id decode path around auth-config selection. (crates/tinyconnectors/src/client/route/direct_tests.rs async fn drops_only_a_row_with_no_id_at_all() {)
  • unit — Asserts the old `{toolkit, entity_id}` link body is rejected with HTTP 400 by the fake.: Guards the fake so the other tests prove the new body rather than a permissive mock. (crates/tinyconnectors/src/client/route/direct_tests.rs impl Transport for FakeTransport {)
  • unit — Asserts an empty entity id falls back to `default` in the `user_id` field.: Updated to the new field name; the tests lane did not flag a problem with it. (crates/tinyconnectors/src/client/route/direct_tests.rs async fn falls_back_to_the_default_entity() {)

Before merge

None.

How this fits together

flowchart LR
  n0["FakeTransport<br/>changed"]:::changed
  n1["drops_only_a_row_with_no_id_at_all<br/>changed"]:::changed
  n2["falls_back_to_the_default_entity<br/>changed"]:::changed
  n3["...lt_with_an_error_and_no_verdict_as_failed<br/>changed"]:::changed
  n4["route"]:::impacted
  n5["replying"]:::impacted
  n6["linking"]:::impacted
  n7["auth_config_for"]:::impacted
  n1 -->|calls| n4
  n1 -->|tests| n4
  n1 -->|calls| n5
  n1 -->|tests| n5
  n2 -->|calls| n6
  n2 -->|tests| n6
  n3 -->|calls| n4
  n3 -->|tests| n4
  n3 -->|calls| n5
  n3 -->|tests| n5
  n4 -->|uses| n0
  n6 -->|calls| n5
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The critique lane described the port from the proxy-shaped body to the v3 shape as sound, with the tests covering reuse, creation, managed-config preference, error wrapping, and rejection of the old body shape, and reported no defects.
  • Lane summary: The change ports the `authorize` method from the proxy-shaped body (toolkit + entity_id) to the v3 shape (auth_config_id + user_id), adding `auth_config_for` to look up or create the auth config before linking. The logic is sound and the tests cover both the reuse and creation paths, the preference for Composio-managed configs, the error wrapping, and the validation that the old body shape is rejected. I see no reportable defects.

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The security lane reported that the toolkit slug is percent-encoded into the query, no credential is logged or embedded, and it found no reachable input that breaks the new path.
  • Lane summary: The change reworks direct-mode authorization to fetch or create a toolkit auth config and send the two fields v3 requires, with an error-mapping helper; the toolkit slug is percent-encoded into the query and no credential is logged or embedded. The accompanying tests were updated coherently and I found no reachable input that breaks the new path.

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The direct route now resolves (or creates) a Composio-managed auth config and links with only `{auth_config_id, user_id}`; the new tests genuinely cover config reuse, creation, managed-config preference, the missing-id decode failure and the default entity, and they script exact request bodies so the old proxy shape fails them. The gap is in the error path: the only refusal test never reaches the `map_err`s it appears to guard, and the documented pass-through branch of `refused_request` is unpinned.

commits

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The commits lane reported no sensitive content in committed files.
  • Lane summary: No sensitive content in committed files.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The description lane found the body describes the diff accurately, including the dropped extra params and scopes, the extra round-trips, the untouched proxy route, the new tests, the fake's v3-style validation, and the untested-against-live-Composio caveat, with no discrepancies.
  • Lane summary: The body describes the diff accurately: the direct route now resolves the toolkit to an auth config (paged `GET /auth_configs?toolkit_slug=…`, preferring a Composio-managed entry), creates a managed config on `POST /auth_configs` when none is usable, links with only `{auth_config_id, user_id}`, and maps 400/422 to `Error::Authorize`; the dropped extra params/scopes, the extra round-trips, and the proxy route being untouched are all called out, as are the new tests and the fake's v3-style body validation. The one thing neither the diff nor the description can settle is whether live Composio accepts these shapes, which the body already flags as unverified. No discrepancies between the description and the change were found.
Evidence and run details
  • Models: ladder/vectors-oai3, deepseek-v4-flash, deepseek-v4.1-flash
  • Spend: $0.015499
  • Completion usage: incomplete; some provider completion costs are unavailable.
  • Tokens: 284536 input · 60393 output · 93056 cached · 500 embedding
  • Updated: 2026-11-11T13:17:50Z
Head State Pass summary
18eabe050445 ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 2026-11-11T11:00:15Z)
138fdc8b984f ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 2026-11-11T13:17:50Z)

tinysweeper 0.1.2

@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 940c7ff8-9949-4796-ab33-085e90bd4f06
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

tinysweeper found nothing blocking. Approving.

Diagnostics

Total spend: $0.0042

@M3gA-Mind
M3gA-Mind merged commit 4d40182 into tinyhumansai:main Nov 11, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Direct-mode Composio authorize sends a body that Composio v3 /connected_accounts/link rejects with a 400 validation error

1 participant