Repository navigation
Composio direct mode surfaces entity-id mismatch as an opaque HTTP 400 with the response body discarded #5731
Description
Summary
In Composio direct mode, an entity-id mismatch on action execution surfaces as a bare Composio direct request failed with HTTP 400 Bad Request — the response body, which contains Composio's precise error and its suggested fix, is discarded. Users have no way to discover the actual problem.
Problem
What happened vs expected: with a Gmail connection created through Composio's own dashboard, every execute/sync fails 400. The direct client (vendor/tinymemory/core/src/sync/pipelines/composio/client.rs, execute_direct) does let _ = response.bytes().await; before bailing with just the status line. The discarded body was:
{"error":{"message":"Connected account user ID does not match the provided user ID. …","code":1812,
"slug":"ActionExecute_ConnectedAccountEntityIdMismatch","status":400,
"suggested_fix":"The connected_account_id you provided belongs to a different entity. …"}}Root cause of the mismatch itself: [composio] entity_id defaults to "default", but connections created via Composio's dashboard are filed under a generated user id (pg-test-<uuid> in my case). The app sends both user_id: "default" and the connected_account_id → guaranteed 400 for any dashboard-connected account, presented as an opaque failure.
Steps to reproduce:
- Direct mode with your own Composio API key; leave
entity_id = "default". - Connect Gmail via Composio's dashboard (Connect New → OAuth), not via the app.
- Trigger any action/sync → opaque
HTTP 400 Bad Requestin logs; sync retries forever. - Same request replayed with the connection's actual
user_idsucceeds.
Version/platform: v0.63.14, macOS, direct mode.
Solution (optional)
Two independent improvements:
- Surface the body — include (redacted)
error.message/slug/suggested_fixin the bail message instead of discarding the bytes. Turns a dead end into a self-explanatory fix. - Stop guessing the entity — the app already knows the
connected_account_id; it can resolve that account'suser_idfrom/connected_accounts(it lists them elsewhere) and use it for execute, or validate/healentity_idat connect time, instead of requiring the config value to match by luck.
Workaround: set [composio] entity_id to the connection's user_id (visible via GET /api/v3/connected_accounts).
Acceptance criteria
- Repro gone — a dashboard-created connection either works out of the box (entity resolved from the account) or fails with Composio's actual message including the suggested fix.
- Regression safety — a test pins that a non-2xx direct response propagates the body's error message.
- Diff coverage ≥ 80% — the fix PR meets the changed-lines coverage gate (Vitest + cargo-llvm-cov, enforced by
.github/workflows/ci-lite.yml).
Related
—
Activity
senamakel commentedon Oct 9, 2026
Triage: valid — Current review confirms this is a concrete, in-scope engineering issue or request: “Composio direct mode surfaces entity-id mismatch as an opaque HTTP 400 with the response body discarded”. No complete resolution is evident in current main.
senamakel commentedon Oct 9, 2026
I checked this against current
main(17675ff). The code has moved since the report.vendor/tinymemory/core/src/sync/pipelines/composio/client.rsno longer exists. Composio direct-mode calls now go through the vendoredtinyconnectorscrate, pinned atca3d5ff(v0.13.0).Part 1, surfacing the body: mostly done.
vendor/tinyconnectors/crates/tinyconnectors/src/client/http.rs:271-306(status_message) no longer discards a non-2xx body. It readserror.message(or a top-levelmessage), redacts account and entity identifiers, truncates to 240 characters, and returnsHTTP <status>: <message>.client/direct_read_tests.rs:124covers this for a 400. The entity-mismatch case would now readHTTP 400: Connected account user ID does not match the provided user ID. …. Composio'sslugandsuggested_fixare still dropped, though, so the acceptance criterion "including the suggested fix" isn't fully met.Part 2, resolving the entity: not done.
client/route/direct.rs:359-365(execute) still sends the configuredentity_idnext toconnected_account_id. A connection created in the Composio dashboard under a generated user id will still get a 400 unless[composio] entity_idis set to match.Both remaining pieces belong in
tinyhumansai/tinyconnectors:suggested_fixinstatus_message, redacted and bounded the same way;user_id, either on execute or at connect time.After that, this repo only needs to move its
vendor/tinyconnectorspin. Leaving this open until then.