Skip to content

[codex] Fix Composio connected integration matching#1229

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
jwalin-shah:codex/1151-context-chat
May 5, 2026
Merged

[codex] Fix Composio connected integration matching#1229
senamakel merged 1 commit into
tinyhumansai:mainfrom
jwalin-shah:codex/1151-context-chat

Conversation

@jwalin-shah
Copy link
Copy Markdown
Contributor

@jwalin-shah jwalin-shah commented May 5, 2026

Summary

  • Normalizes Composio connection status checks in the Rust runtime to match the existing UI behavior.
  • Normalizes toolkit slugs before cache reconciliation, tool discovery, periodic sync, and Slack ingestion checks.
  • Adds regression coverage for Slack/Telegram connections with lowercase/whitespace status and toolkit values.

Problem

  • Settings could show Slack or Telegram as connected while chat/runtime code dropped the integration because backend status/toolkit strings were compared too literally.
  • This made connected read tools unavailable to the agent even though the user had completed the connection flow.

Solution

  • Added ComposioConnection::is_active() and ComposioConnection::normalized_toolkit() as the boundary helpers for external Composio strings.
  • Replaced raw active-status/toolkit comparisons in the relevant Composio and Slack ingestion paths.
  • Added a mock-backend regression test that keeps Slack/Telegram read tools available while still filtering an admin Slack tool.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per docs/TESTING-STRATEGY.md
  • N/A: Diff coverage ≥ 80% — not run locally; expected from CI.
  • N/A: Coverage matrix updated — behavior-only runtime bug fix for existing Composio integration behavior.
  • N/A: All affected feature IDs from the matrix are listed in the PR description under ## Related — no matrix feature ID identified for this narrow bug fix.
  • No new external network dependencies introduced (mock backend used per docs/TESTING-STRATEGY.md)
  • N/A: Manual smoke checklist updated if this touches release-cut surfaces — no release smoke checklist change required.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime impact: connected Slack/Telegram integrations should be exposed to chat consistently with Settings UI connection state.
  • Compatibility: only broadens accepted external Composio status/toolkit spelling at the boundary; does not add new tool permissions.
  • Local validation: pnpm debug rust composio -- --nocapture and cargo check --manifest-path Cargo.toml passed earlier on this branch; no further Rust validation was run after machine-memory constraints were identified.

Related

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved connection status detection to properly handle whitespace and case variations, ensuring connections are accurately identified as active or inactive regardless of formatting inconsistencies.
  • Refactor

    • Standardized toolkit name handling across the system to consistently normalize names by removing whitespace and converting to lowercase, enhancing reliability of connection and toolkit matching.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

This PR standardizes Composio connection status checking and toolkit name handling by introducing is_active() and normalized_toolkit() methods on ComposioConnection, then applying these methods throughout bus, ops, tools, periodic, and Slack integration code to replace scattered status-string matching and inline string normalization.

Changes

Composio Connection Normalization

Layer / File(s) Summary
API & Types
src/openhuman/composio/types.rs
Added ComposioConnection::is_active() (trim and case-insensitive match against "ACTIVE"/"CONNECTED") and ComposioConnection::normalized_toolkit() (trim and lowercase toolkit). Includes unit tests verifying both methods handle whitespace and case variations.
Core Implementation
src/openhuman/composio/bus.rs, src/openhuman/composio/ops.rs, src/openhuman/composio/tools.rs
Updated connection-readiness polling, active-connection counting, connected-toolkit set building, and toolkit allowlist construction to use is_active() and normalized_toolkit() instead of explicit status-string matching and inline normalization.
Periodic Sync Scheduler
src/openhuman/composio/periodic.rs
Modified connection filtering and provider lookup to use is_active() and normalized_toolkit(); updated last-sync "due" key to use normalized toolkit instead of raw toolkit.
Slack Integration
src/openhuman/memory/slack_ingestion/rpc.rs
Changed Slack connection filtering in sync_trigger_rpc and sync_status_rpc to use normalized_toolkit() == "slack" and is_active() instead of case-sensitive string comparisons.
Tests
src/openhuman/composio/ops_tests.rs, src/openhuman/composio/types.rs
Added fetch_connected_integrations_treats_slack_and_telegram_status_like_ui mock test verifying normalization of toolkit names and status variations; removed prior mock test for empty-result scenario; unit tests for new methods in types.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • tinyhumansai/openhuman#1195: Directly implements the connection liveness and toolkit normalization pattern that complements composio integration and Slack sync detection.
  • tinyhumansai/openhuman#776: Previously modified Composio connection-listing and integration-reconciliation logic with similar status-matching semantics.

Suggested reviewers

  • graycyrus

Poem

🐰 Toolkit names and statuses, now they align,
Trimmed and lowercased, working just fine.
No more scattered checks, the API's now clean,
Slack and Telegram flow through the routine!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing Composio connected integration matching logic through normalization.
Linked Issues check ✅ Passed The PR addresses issue #1151 by normalizing connection status and toolkit matching in the Rust runtime, enabling connected integrations to expose read tools to chat consistently with UI behavior.
Out of Scope Changes check ✅ Passed All changes are focused on normalizing Composio connection status and toolkit slug handling across the codebase, directly supporting the linked issue objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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


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 and usage tips.

@jwalin-shah jwalin-shah marked this pull request as ready for review May 5, 2026 14:52
@jwalin-shah jwalin-shah requested a review from a team May 5, 2026 14:52
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/openhuman/composio/periodic.rs (1)

174-222: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cache key mismatch: due lookup uses normalized toolkit, but record_sync_success is called with the raw conn.toolkit.

Line 174 builds the dedupe key from the normalized toolkit:

let key = (toolkit.clone(), conn.id.clone());   // normalized

But on success at line 209 the timestamp is stored under the raw value:

record_sync_success(&conn.toolkit, &conn.id);   // raw, e.g. " Slack "

For any backend payload where conn.toolkit differs from its normalized form (whitespace, mixed case), the next tick's map.get(&key) lookup will miss the just-written entry and the scheduler will re-fire on every tick — defeating the per-provider sync_interval_secs budget that this whole module exists to enforce.

The same risk exists for the bus path (src/openhuman/composio/bus.rs line 407 calls record_sync_success(&toolkit, …) with the toolkit from the raw event payload).

Proposed fix — record under the normalized key and align logging fields
         tracing::debug!(
-            toolkit = %conn.toolkit,
+            toolkit = %toolkit,
+            raw_toolkit = %conn.toolkit,
             connection_id = %conn.id,
             interval_secs,
             "[composio:periodic] firing sync"
         );
         match provider.sync(&ctx, SyncReason::Periodic).await {
             Ok(outcome) => {
                 tracing::debug!(
-                    toolkit = %conn.toolkit,
+                    toolkit = %toolkit,
                     connection_id = %conn.id,
                     items = outcome.items_ingested,
                     elapsed_ms = outcome.elapsed_ms(),
                     "[composio:periodic] sync ok"
                 );
-                record_sync_success(&conn.toolkit, &conn.id);
+                record_sync_success(&toolkit, &conn.id);
                 fired += 1;
             }
             Err(e) => {
                 tracing::warn!(
-                    toolkit = %conn.toolkit,
+                    toolkit = %toolkit,
                     connection_id = %conn.id,
                     error = %e,
                     "[composio:periodic] sync failed (will retry next tick)"
                 );

And in bus.rs consider normalizing before recording, or routing the success record through the same normalized key path used by the scheduler.

🤖 Prompt for 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.

In `@src/openhuman/composio/periodic.rs` around lines 174 - 222, The dedupe key
uses the normalized variable toolkit but on success you call
record_sync_success(&conn.toolkit, &conn.id) which writes under the raw toolkit
string and breaks lookups; change the success path to call
record_sync_success(&toolkit, &conn.id) (and update the tracing fields to log
the normalized toolkit where appropriate) and apply the same
normalization-before-recording fix in the bus path (replace calls that pass the
raw toolkit with the normalized toolkit or route through the same normalization
helper) so writes and reads use the identical key.
src/openhuman/composio/ops.rs (1)

398-413: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

resolve_toolkit_for_connection returns the raw toolkit while callers feed it straight into get_provider.

Now that the rest of this module standardizes on normalized_toolkit() (lines 613, 803, 833), the helper at line 412 is the last raw-toolkit hold-out. Its callers (composio_get_user_profile line 424–426 and composio_sync line 475–477) pass the result directly into get_provider(&toolkit), and the registry is populated with canonical lowercase keys. If the backend ever returns " Slack " for a connection's toolkit, get_provider will miss and the user gets "no native provider registered for toolkit ' Slack '" even though the rest of the system treats that connection as a valid Slack one.

The composio_delete_connection path also uses this value (line 137) to scope identity-facet deletion and the emitted ComposioConnectionDeleted event — also better off normalized.

Proposed fix
-    Ok(conn.toolkit)
+    Ok(conn.normalized_toolkit())
🤖 Prompt for 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.

In `@src/openhuman/composio/ops.rs` around lines 398 - 413, The helper
resolve_toolkit_for_connection returns the raw toolkit string; change it to
return the normalized form by passing conn.toolkit through normalized_toolkit()
before returning so callers (composio_get_user_profile, composio_sync and places
that emit ComposioConnectionDeleted) receive the canonical lowercase/trimmed
toolkit key; locate resolve_toolkit_for_connection, call
normalized_toolkit(conn.toolkit) (or the module's equivalent) and return that
result instead of raw conn.toolkit to ensure registry lookups via
get_provider(&toolkit) succeed.
src/openhuman/memory/slack_ingestion/rpc.rs (1)

78-94: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align toolkit normalization with periodic.rs.

To maintain consistency across similar code paths, normalize conn.toolkit before passing to ProviderContext. src/openhuman/composio/periodic.rs (line 161 onward) extracts and passes the normalized toolkit to the context; the manual trigger path in slack_ingestion/rpc.rs currently passes the raw value. Even though SlackProvider::sync hardcodes state operations with the string "slack", normalizing here prevents potential issues if future code relies on consistent context values.

Proposed fix
 for conn in candidates {
+    let toolkit = conn.normalized_toolkit();
     let ctx = ProviderContext {
         config: Arc::clone(&config_arc),
         client: client.clone(),
-        toolkit: conn.toolkit.clone(),
+        toolkit,
         connection_id: Some(conn.id.clone()),
     };
🤖 Prompt for 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.

In `@src/openhuman/memory/slack_ingestion/rpc.rs` around lines 78 - 94, The manual
sync loop constructs ProviderContext with conn.toolkit unnormalized; align it
with periodic.rs by normalizing the toolkit first (use the same helper used in
periodic.rs—e.g., normalize_toolkit(...) or Toolkit::normalize(...) to produce
toolkit_normalized) and pass toolkit: toolkit_normalized into ProviderContext
instead of conn.toolkit so the context receives the normalized value.
🧹 Nitpick comments (2)
src/openhuman/composio/ops.rs (1)

799-810: 💤 Low value

Optional: reuse the helper for symmetry with the connection path.

The connection-side derivation at line 833 uses c.normalized_toolkit(), while the toolkits-allowlist branch here re-implements trim().to_ascii_lowercase() inline against plain String values. Behaviorally equivalent; just calling out that if you later change the canonicalization rule (e.g. NFC unicode normalization) you'd have two places to update. A small free function in types.rs (fn normalize_toolkit(s: &str) -> String) used by both ComposioConnection::normalized_toolkit and this loop would keep them in lockstep — fine to defer.

🤖 Prompt for 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.

In `@src/openhuman/composio/ops.rs` around lines 799 - 810, Replace the inline
canonicalization in the allowlisted_toolkits creation (the map using
trim().to_ascii_lowercase()) with a shared helper so both sides stay consistent:
add a fn normalize_toolkit(s: &str) -> String in types.rs, change
ComposioConnection::normalized_toolkit to call that helper, and update the
client.list_toolkits().await mapping (the allowlisted_toolkits variable) to call
normalize_toolkit(&toolkit) instead of re-implementing trim/to_ascii_lowercase.
src/openhuman/composio/ops_tests.rs (1)

444-513: 💤 Low value

Good regression coverage; consider an explicit negative assertion for the admin tool.

The slack.tools.len() == 1 + name check implicitly verifies that SLACK_DELETE_CHANNEL was filtered out, which is the second half of the PR's claim ("still filtering an admin Slack tool"). To make the intent self-documenting (and avoid a future contributor "fixing" the test by relaxing it when the default scope changes), an explicit assertion that the admin tool is absent would help:

Suggested addition
     assert!(slack.connected);
     assert_eq!(slack.tools.len(), 1);
     assert_eq!(slack.tools[0].name, "SLACK_FETCH_CONVERSATION_HISTORY");
+    assert!(
+        slack.tools.iter().all(|t| t.name != "SLACK_DELETE_CHANNEL"),
+        "admin-scoped Slack tool must be filtered out under default user scope"
+    );

Also note the test's correctness depends on the default UserScopePref blocking admin actions — worth a one-line comment so the dependency is visible.

🤖 Prompt for 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.

In `@src/openhuman/composio/ops_tests.rs` around lines 444 - 513, The test
fetch_connected_integrations_treats_slack_and_telegram_status_like_ui should
explicitly assert that the admin Slack tool is filtered out: after locating
slack (variable slack) add an assertion that none of slack.tools have name
"SLACK_DELETE_CHANNEL" (e.g., assert!(!slack.tools.iter().any(|t| t.name ==
"SLACK_DELETE_CHANNEL"))), and add a one-line comment near the test noting it
depends on the default UserScopePref blocking admin actions so the intent is
clear.
🤖 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.

Outside diff comments:
In `@src/openhuman/composio/ops.rs`:
- Around line 398-413: The helper resolve_toolkit_for_connection returns the raw
toolkit string; change it to return the normalized form by passing conn.toolkit
through normalized_toolkit() before returning so callers
(composio_get_user_profile, composio_sync and places that emit
ComposioConnectionDeleted) receive the canonical lowercase/trimmed toolkit key;
locate resolve_toolkit_for_connection, call normalized_toolkit(conn.toolkit) (or
the module's equivalent) and return that result instead of raw conn.toolkit to
ensure registry lookups via get_provider(&toolkit) succeed.

In `@src/openhuman/composio/periodic.rs`:
- Around line 174-222: The dedupe key uses the normalized variable toolkit but
on success you call record_sync_success(&conn.toolkit, &conn.id) which writes
under the raw toolkit string and breaks lookups; change the success path to call
record_sync_success(&toolkit, &conn.id) (and update the tracing fields to log
the normalized toolkit where appropriate) and apply the same
normalization-before-recording fix in the bus path (replace calls that pass the
raw toolkit with the normalized toolkit or route through the same normalization
helper) so writes and reads use the identical key.

In `@src/openhuman/memory/slack_ingestion/rpc.rs`:
- Around line 78-94: The manual sync loop constructs ProviderContext with
conn.toolkit unnormalized; align it with periodic.rs by normalizing the toolkit
first (use the same helper used in periodic.rs—e.g., normalize_toolkit(...) or
Toolkit::normalize(...) to produce toolkit_normalized) and pass toolkit:
toolkit_normalized into ProviderContext instead of conn.toolkit so the context
receives the normalized value.

---

Nitpick comments:
In `@src/openhuman/composio/ops_tests.rs`:
- Around line 444-513: The test
fetch_connected_integrations_treats_slack_and_telegram_status_like_ui should
explicitly assert that the admin Slack tool is filtered out: after locating
slack (variable slack) add an assertion that none of slack.tools have name
"SLACK_DELETE_CHANNEL" (e.g., assert!(!slack.tools.iter().any(|t| t.name ==
"SLACK_DELETE_CHANNEL"))), and add a one-line comment near the test noting it
depends on the default UserScopePref blocking admin actions so the intent is
clear.

In `@src/openhuman/composio/ops.rs`:
- Around line 799-810: Replace the inline canonicalization in the
allowlisted_toolkits creation (the map using trim().to_ascii_lowercase()) with a
shared helper so both sides stay consistent: add a fn normalize_toolkit(s: &str)
-> String in types.rs, change ComposioConnection::normalized_toolkit to call
that helper, and update the client.list_toolkits().await mapping (the
allowlisted_toolkits variable) to call normalize_toolkit(&toolkit) instead of
re-implementing trim/to_ascii_lowercase.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 903beb7c-488c-4393-8af4-3b317078949a

📥 Commits

Reviewing files that changed from the base of the PR and between e945390 and aa70e9b.

📒 Files selected for processing (7)
  • src/openhuman/composio/bus.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/ops_tests.rs
  • src/openhuman/composio/periodic.rs
  • src/openhuman/composio/tools.rs
  • src/openhuman/composio/types.rs
  • src/openhuman/memory/slack_ingestion/rpc.rs

@senamakel senamakel merged commit 377a326 into tinyhumansai:main May 5, 2026
22 of 23 checks passed
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix chat access to connected Telegram/Slack messages and summaries

3 participants