Skip to content

composio: preserve Composio tool error semantics instead of bucketing as 502 (#1797)#1827

Merged
senamakel merged 9 commits into
tinyhumansai:mainfrom
CodeGhost21:cursor/a03-1797-composio-error-mapping
May 16, 2026
Merged

composio: preserve Composio tool error semantics instead of bucketing as 502 (#1797)#1827
senamakel merged 9 commits into
tinyhumansai:mainfrom
CodeGhost21:cursor/a03-1797-composio-error-mapping

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented May 15, 2026

Summary

  • Add a shared Composio execute path that validates/normalizes arguments before dispatch and classifies failures with grep-friendly [composio:error:<class>] prefixes instead of generic gateway wording.
  • Fix high-traffic tool inputs: calendar timeMin/timeMax bare dates → RFC 3339, NOTION_FETCH_DATA fetch_type inference, Gmail send/label required-field checks, and Slack history rate-limit backoff.
  • Expose formatComposioToolError in the app for UI-friendly classified error copy.

Problem

Composio support confirmed there were no platform 502s for our org — tool-level validation, missing OAuth scopes, and upstream provider 429/403 failures were being surfaced to agents/users as generic 502/gateway instability (#1797).

Solution

  • execute_prepare.rs — local validation + normalization before any HTTP call.
  • error_mapping.rs — classify provider/transport failures; re-label embedded provider errors inside 502 bodies as validation/upstream_provider, not gateway.
  • execute_dispatch.rs — single dispatch used by composio_execute, per-action tools, and RPC; includes Slack SLACK_FETCH_CONVERSATION_HISTORY rate-limit retries.
  • formatComposioToolError — strips classified prefixes for UI.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — CI coverage.yml will enforce; full pnpm test:coverage + pnpm test:rust not run in this session.
  • Coverage matrix updated — N/A: behaviour-only change in composio execute/error path
  • All affected feature IDs from the matrix are listed in the PR description under ## RelatedN/A
  • No new external network dependencies introduced (mock backend used per testing strategy)
  • Manual smoke checklist updated — N/A
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Desktop agent + RPC Composio execute paths return actionable, classified errors.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: cursor/a03-1797-composio-error-mapping
  • Commit SHA: 711e27e8c9badd99f47faed40a9e597f8347b378

Validation Run

  • pnpm --filter openhuman-app format:check
  • pnpm typecheck
  • Focused tests: cargo test --lib composio::execute + composio::error_mapping, Vitest formatters.test.ts
  • Rust fmt/check: cargo fmt, cargo check --lib
  • Tauri fmt/check: N/A

Validation Blocked

  • command: pnpm test:coverage + pnpm test:rust (full diff-cover)
  • error: Not run locally — rely on CI coverage workflow.
  • impact: May need follow-up if diff-cover < 80%.

Behavior Changes

  • Intended behavior change: Classify Composio failures by root cause; validate/normalize known bad argument shapes before dispatch.
  • User-visible effect: Actionable errors instead of generic 502 messaging.

Parity Contract

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this PR
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • User-facing, classified Composio error messages with clearer fallbacks across UI and backend.
    • Automatic argument validation/normalization for Google Calendar, Notion, and Gmail.
    • Centralized dispatch with upstream-aware rate-limit retry for select tools; dispatcher-preserved error semantics.
  • Bug Fixes

    • UTF-8–safe truncation of event payloads in debug logs.
  • Tests

    • Expanded coverage for error classification/formatting, argument preparation, dispatch, and retry behavior.

Review Change Stack

CodeGhost21 and others added 2 commits May 15, 2026 23:37
…inyhumansai#1814)

Bind payload JSON once and slice via floor_char_boundary so multi-byte
characters straddling byte 500 no longer abort the core thread (OPENHUMAN-TAURI-KC).

Co-authored-by: Cursor <cursoragent@cursor.com>
…inyhumansai#1797)

Validate and normalize high-traffic Composio actions before dispatch, classify
provider failures with grep-friendly error classes, and retry Slack history
rate limits so agents see actionable messages instead of generic gateway errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
@CodeGhost21 CodeGhost21 requested a review from a team May 15, 2026 18:24
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Composio error classification/formatting, argument normalization/validation, a shared execute dispatcher with retry/backoff (Slack-specific rate-limit retries), and wires these through client/ops/tools plus a frontend formatter and UTF‑8-safe socket logging.

Changes

Composio Error Classification and Tool Validation

Layer / File(s) Summary
Error classification and formatting infrastructure
src/openhuman/composio/error_mapping.rs, src/openhuman/composio/error_mapping_tests.rs
ComposioErrorClass enum and predicates classify errors; classify_composio_error, format_provider_error, and remap_transport_error produce [composio:error:<class>] tagged messages with class-specific guidance and tests.
Tool-specific argument preparation and validation
src/openhuman/composio/execute_prepare.rs, src/openhuman/composio/execute_prepare_tests.rs
prepare_execute_arguments coerces/validates arguments into a JSON object, promotes bare YYYY-MM-DD calendar bounds to RFC3339 midnight UTC, infers fetch_type for Notion, and validates Gmail send/label required fields; unit tests cover these rules.
Execute dispatch with rate-limit retry logic
src/openhuman/composio/execute_dispatch.rs, src/openhuman/composio/execute_dispatch_tests.rs
execute_composio_action validates tool slug, prepares arguments, dispatches via a retry loop, converts transport failures to remapped error strings, and applies Slack-specific exponential backoff on detected rate-limit errors; includes mock-backed tests.
Client and operations layer integration
src/openhuman/composio/client.rs, src/openhuman/composio/ops.rs, src/openhuman/composio/tools.rs, src/openhuman/composio/action_tool.rs, src/openhuman/composio/mod.rs
ComposioClient now calls prepare_execute_arguments and remaps transport/provider errors. ops/tools/action_tool now call execute_dispatch::execute_composio_action and propagate dispatcher-classified errors unchanged. New submodules are publicly exposed. Tests updated to assert classified error formats.
Frontend error classification formatter
app/src/lib/composio/formatters.ts, app/src/lib/composio/formatters.test.ts
Adds formatComposioToolError to strip [composio:error:<class>] prefixes, extract message bodies, and map known classes to UI-friendly labels; tests verify prefix stripping and passthrough behavior.
UTF-8 safe event payload truncation
src/openhuman/socket/event_handlers.rs
handle_sio_event now truncates debug payloads at a UTF-8-safe character boundary using floor_char_boundary; adds a test validating behavior with multi-byte payloads.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant Dispatcher as execute_composio_action
  participant Prepare as prepare_execute_arguments
  participant Retry as execute_with_retries
  participant Client as ComposioClient
  participant ErrorMap as error_mapping::remap_transport_error
  Caller->>Dispatcher: call(tool, arguments)
  Dispatcher->>Prepare: prepare_execute_arguments(tool, arguments)
  Dispatcher->>Retry: execute_with_retries(client, tool, args)
  Retry->>Client: execute_with_auth_retry_inner(...)
  Client-->>Retry: provider response / transport error
  Retry->>Dispatcher: response or error
  alt transport failure
    Dispatcher->>ErrorMap: remap_transport_error(tool, raw)
    ErrorMap-->>Caller: "[composio:error:<class>] …"
  else provider unsuccessful
    Dispatcher->>ErrorMap: format_provider_error(tool, resp.error)
    ErrorMap-->>Caller: "[composio:error:<class>] …"
  else success
    Dispatcher-->>Caller: ComposioExecuteResponse
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • graycyrus
  • senamakel

"🐰 I nibble at the tangled thread,
Trim prefixes, sew messages instead.
Dates grow tails of midnight Z,
Slack takes breaths when tokens plea.
Hops of tests — a tidy spread."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing Composio tool error classification by preserving error semantics instead of misclassifying them as 502 gateway failures.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from issue #1797: validates/normalizes inputs for Google Calendar (RFC3339 dates), Notion (fetch_type inference), Gmail (required fields), implements Slack rate-limit backoff, and maps errors with classified [composio:error:] prefixes for proper root-cause distinction.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objectives: error classification, argument preparation, local validation, Slack rate-limit retry logic, and supporting UI formatting. The socket event handler UTF-8 truncation fix is a minimal related refactoring that supports safe error logging.

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


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 15, 2026
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.

Actionable comments posted: 2

🧹 Nitpick comments (6)
src/openhuman/composio/execute_dispatch.rs (1)

84-84: 💤 Low value

Rate-limit retry is Slack-specific but uses generic predicate.

The retry logic at line 84 checks tool == SLACK_HISTORY before retrying on rate-limits, but the is_rate_limited predicate (lines 102-109) is generic. If other tools need rate-limit retry in the future, this condition would need to be generalized.

Consider whether the Slack-specific check should be documented or if a more general approach (e.g., a list of tools that support rate-limit retry) would be clearer for future maintainers.

🤖 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/execute_dispatch.rs` at line 84, The retry branch
currently gates rate-limit retries with a Slack-specific check (tool ==
SLACK_HISTORY) while using the generic predicate is_rate_limited(err_text);
replace that hard equality with a clearer, maintainable policy: define and use a
named collection/constant (e.g., RATE_LIMIT_RETRY_TOOLS) and check membership
(e.g., RATE_LIMIT_RETRY_TOOLS.contains(&tool)) or, if Slack must remain the only
case, add a short comment documenting why only SLACK_HISTORY should retry;
update the conditional using symbols tool, SLACK_HISTORY, is_rate_limited,
attempt, and RATELIMIT_MAX_ATTEMPTS accordingly so future tools can be added
without changing logic.
src/openhuman/composio/execute_prepare.rs (2)

5-32: ⚡ Quick win

Add debug logging for validation rejections.

When prepare_execute_arguments returns an error (lines 11-14, 28, 33), there's no diagnostic trace. Given that this is a new validation layer that may reject previously-accepted calls, logging the rejection reason would aid debugging.

📊 Add validation failure logging
         Some(other) => {
+            tracing::debug!(
+                tool = %tool,
+                got_type = ?other,
+                "[composio][prepare] arguments must be object, rejecting"
+            );
             return Err(format!(
                 "composio: `{tool}` arguments must be a JSON object, got {}",
                 other
             ));
         }

And similarly for the tool-specific validation failures at lines 25-29.

As per coding guidelines: "In Rust, use log / tracing at debug or trace level for development-oriented diagnostics on new/changed flows, including logs at entry/exit points, branch decisions, external calls, retries/timeouts, state transitions, and error handling paths."

🤖 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/execute_prepare.rs` around lines 5 - 32,
prepare_execute_arguments currently returns errors without logging; update it to
emit debug/tracing logs whenever it rejects arguments: log the incoming tool and
arguments and the error message before returning Err in the object-type mismatch
branch inside prepare_execute_arguments, and likewise log validation failures
just before propagating errors from normalize_calendar_time_bounds,
ensure_notion_fetch_type, validate_gmail_send_email, and
validate_gmail_add_label (include the tool name and the specific validation
error). Use the project's tracing/log macros at debug or trace level and keep
the logs concise to aid debugging.

61-62: ⚖️ Poor tradeoff

Date format heuristic relies on byte indexing.

The check s.len() == 10 && s.as_bytes().get(4) == Some(&b'-') && s.as_bytes().get(7) == Some(&b'-') works for ASCII dates but won't catch invalid dates like 2026-99-99. This is acceptable as a pre-flight check (the provider will reject invalid dates), but consider whether tighter validation via a date-parsing crate would catch user errors earlier.

🤖 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/execute_prepare.rs` around lines 61 - 62, The current
heuristic in the if branch that checks s.len() and byte positions (s.len() == 10
&& s.as_bytes().get(4) == Some(&b'-') && s.as_bytes().get(7) == Some(&b'-'))
only validates ASCII layout and can accept impossible dates; replace this
pre-flight with an explicit date parse (e.g., using
chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") or time::Date::parse) to
validate the YYYY-MM-DD value: if parsing succeeds, produce the normalized
RFC3339 midnight string (e.g., format "{s}T00:00:00Z" or build from the parsed
date) and otherwise fall back to the existing behavior (or return None/propagate
error) so invalid dates like "2026-99-99" are rejected earlier; update the block
that currently inspects s and bytes accordingly.
src/openhuman/composio/error_mapping.rs (2)

114-118: 💤 Low value

Toolkit extraction assumes specific naming pattern.

The toolkit name is derived by taking the first segment before _, which works for GMAIL_SEND_EMAILgmail but would fail for tools that don't follow the TOOLKIT_ACTION pattern (e.g., a tool named just SEARCH would produce search, which is correct, but an edge case like GMAIL alone would also work).

This is defensive and unlikely to be an issue given Composio's naming conventions, but consider documenting the assumption or adding a fallback for unexpected patterns.

🤖 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/error_mapping.rs` around lines 114 - 118, The current
toolkit extraction (variable toolkit computed from
tool.split('_').next().unwrap_or("integration").to_ascii_lowercase()) assumes
tools follow TOOLKIT_ACTION naming; update it to be defensive by using the whole
tool string when no '_' is present (i.e., if tool.contains('_') take the segment
before '_' otherwise use tool), convert to_ascii_lowercase(), and only fall back
to "integration" if the resulting string is empty; also add a brief inline
comment near this logic explaining the naming assumption so future readers know
why this behavior exists (refer to the toolkit variable and the split('_')
extraction).

33-58: ⚡ Quick win

Consider adding debug logging for classification decisions.

The classify_composio_error function makes important classification decisions (especially the gateway-vs-provider distinction at line 44) but produces no diagnostics. When debugging misclassified errors, knowing why a particular class was chosen would be valuable.

📊 Add classification logging
 pub fn classify_composio_error(tool: &str, message: &str) -> ComposioErrorClass {
     let lower = message.to_ascii_lowercase();
     if is_validation_shape(&lower) {
+        tracing::debug!(tool = %tool, class = "validation", "[composio][error_mapping] classified");
         return ComposioErrorClass::Validation;
     }
     if is_insufficient_scope_shape(&lower) {
+        tracing::debug!(tool = %tool, class = "insufficient_scope", "[composio][error_mapping] classified");
         return ComposioErrorClass::InsufficientScope;
     }
     if is_rate_limited_shape(&lower) {
+        tracing::debug!(tool = %tool, class = "rate_limited", "[composio][error_mapping] classified");
         return ComposioErrorClass::RateLimited;
     }
     if is_gateway_transport_shape(&lower) && !is_embedded_provider_failure(&lower) {
+        tracing::debug!(tool = %tool, class = "gateway", "[composio][error_mapping] classified");
         return ComposioErrorClass::Gateway;
     }
     if is_composio_platform_shape(&lower) {
+        tracing::debug!(tool = %tool, class = "composio_platform", "[composio][error_mapping] classified");
         return ComposioErrorClass::ComposioPlatform;
     }
     if tool.starts_with("GMAIL_")
         || tool.starts_with("SLACK_")
         || tool.starts_with("NOTION_")
         || tool.starts_with("GOOGLECALENDAR_")
     {
+        tracing::debug!(tool = %tool, class = "upstream_provider", "[composio][error_mapping] classified");
         return ComposioErrorClass::UpstreamProvider;
     }
+    tracing::debug!(tool = %tool, class = "other", "[composio][error_mapping] classified");
     ComposioErrorClass::Other
 }

As per coding guidelines: "In Rust, use log / tracing at debug or trace level for development-oriented diagnostics on new/changed flows, including logs at entry/exit points, branch decisions, external calls, retries/timeouts, state transitions, and error handling paths."

🤖 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/error_mapping.rs` around lines 33 - 58, Add
debug/tracing diagnostics to classify_composio_error: log entry with the tool
and original message, then emit a debug/trace for each branch decision (e.g.,
when is_validation_shape, is_insufficient_scope_shape, is_rate_limited_shape,
is_gateway_transport_shape (and the nested !is_embedded_provider_failure),
is_composio_platform_shape, and the provider-tool prefix checks like
tool.starts_with("GMAIL_")/ "SLACK_"/"NOTION_"/"GOOGLECALENDAR_"). Ensure each
log shows which predicate matched (or that none matched) and the resulting
ComposioErrorClass so it’s easy to trace why the gateway-vs-provider path was
chosen; use the project’s tracing/log facade (tracing::debug! or log::debug!)
and keep messages concise.
src/openhuman/composio/execute_dispatch_tests.rs (1)

12-26: ⚡ Quick win

drop(tx) immediately shuts down the test server.

Line 24 drops the oneshot sender right after creation, which closes the receiver channel and triggers graceful shutdown. The test still passes because the async shutdown completes after the test assertions run, but this makes the mock server lifecycle unclear.

🔧 Return the shutdown handle for explicit cleanup
-async fn start_mock_backend(app: Router) -> String {
+async fn start_mock_backend(app: Router) -> (String, oneshot::Sender<()>) {
     let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
     let addr = listener.local_addr().unwrap();
     let (tx, rx) = oneshot::channel::<()>();
     tokio::spawn(async move {
         axum::serve(listener, app)
             .with_graceful_shutdown(async {
                 let _ = rx.await;
             })
             .await
             .unwrap();
     });
-    drop(tx);
-    format!("http://{addr}")
+    (format!("http://{addr}"), tx)
 }

 // In test:
-    let base = start_mock_backend(app).await;
+    let (base, _shutdown) = start_mock_backend(app).await;

Or simply remove the graceful shutdown logic if tests don't need explicit cleanup.

🤖 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/execute_dispatch_tests.rs` around lines 12 - 26, The
test server is being shut down immediately because the oneshot sender `tx` is
dropped in `start_mock_backend`, which closes the receiver `rx` and triggers
`with_graceful_shutdown`; fix by either returning the shutdown handle so the
caller can close the server explicitly (change `start_mock_backend` to return
e.g. `(String, oneshot::Sender<()>)` and do not drop `tx` inside the function)
or remove the graceful shutdown machinery entirely (drop `rx` usage and
`with_graceful_shutdown` and just spawn `axum::serve(listener, app).await`),
updating call sites to use the returned sender for cleanup if you choose the
first option.
🤖 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.

Inline comments:
In `@src/openhuman/composio/ops.rs`:
- Line 268: The current Err(format!("[composio] execute failed: {e}")) in ops.rs
wraps every dispatcher error and can strip or hide a leading classification tag;
change the logic in the execute/error-return site (where variable e is produced)
to detect if e.starts_with("[composio:error:") and if so return Err(e)
unchanged, otherwise return the formatted Err with the "[composio] execute
failed: " prefix; update the branch that produces Err(...) to perform this
conditional so consumers can still match the original dispatcher error tag.

In `@src/openhuman/socket/event_handlers.rs`:
- Around line 35-40: The debug log currently prints raw, truncated payload text
via payload_json and preview_end (using
crate::openhuman::util::floor_char_boundary), which can leak secrets; change the
logging to avoid raw body content and instead log safe metadata such as the
payload byte length and structural info: compute payload_len =
payload_json.len() (or data.as_bytes().len()), and in the log::debug call for
event_name replace &payload_json[..preview_end] with a redacted summary like
"<REDACTED_BODY length={payload_len} chars>" (or include inferred content
type/field keys if available) so the log shows size/structure but never raw body
text. Ensure you update any uses of preview_end/floor_char_boundary accordingly
to avoid slicing into the body when redacting.

---

Nitpick comments:
In `@src/openhuman/composio/error_mapping.rs`:
- Around line 114-118: The current toolkit extraction (variable toolkit computed
from tool.split('_').next().unwrap_or("integration").to_ascii_lowercase())
assumes tools follow TOOLKIT_ACTION naming; update it to be defensive by using
the whole tool string when no '_' is present (i.e., if tool.contains('_') take
the segment before '_' otherwise use tool), convert to_ascii_lowercase(), and
only fall back to "integration" if the resulting string is empty; also add a
brief inline comment near this logic explaining the naming assumption so future
readers know why this behavior exists (refer to the toolkit variable and the
split('_') extraction).
- Around line 33-58: Add debug/tracing diagnostics to classify_composio_error:
log entry with the tool and original message, then emit a debug/trace for each
branch decision (e.g., when is_validation_shape, is_insufficient_scope_shape,
is_rate_limited_shape, is_gateway_transport_shape (and the nested
!is_embedded_provider_failure), is_composio_platform_shape, and the
provider-tool prefix checks like tool.starts_with("GMAIL_")/
"SLACK_"/"NOTION_"/"GOOGLECALENDAR_"). Ensure each log shows which predicate
matched (or that none matched) and the resulting ComposioErrorClass so it’s easy
to trace why the gateway-vs-provider path was chosen; use the project’s
tracing/log facade (tracing::debug! or log::debug!) and keep messages concise.

In `@src/openhuman/composio/execute_dispatch_tests.rs`:
- Around line 12-26: The test server is being shut down immediately because the
oneshot sender `tx` is dropped in `start_mock_backend`, which closes the
receiver `rx` and triggers `with_graceful_shutdown`; fix by either returning the
shutdown handle so the caller can close the server explicitly (change
`start_mock_backend` to return e.g. `(String, oneshot::Sender<()>)` and do not
drop `tx` inside the function) or remove the graceful shutdown machinery
entirely (drop `rx` usage and `with_graceful_shutdown` and just spawn
`axum::serve(listener, app).await`), updating call sites to use the returned
sender for cleanup if you choose the first option.

In `@src/openhuman/composio/execute_dispatch.rs`:
- Line 84: The retry branch currently gates rate-limit retries with a
Slack-specific check (tool == SLACK_HISTORY) while using the generic predicate
is_rate_limited(err_text); replace that hard equality with a clearer,
maintainable policy: define and use a named collection/constant (e.g.,
RATE_LIMIT_RETRY_TOOLS) and check membership (e.g.,
RATE_LIMIT_RETRY_TOOLS.contains(&tool)) or, if Slack must remain the only case,
add a short comment documenting why only SLACK_HISTORY should retry; update the
conditional using symbols tool, SLACK_HISTORY, is_rate_limited, attempt, and
RATELIMIT_MAX_ATTEMPTS accordingly so future tools can be added without changing
logic.

In `@src/openhuman/composio/execute_prepare.rs`:
- Around line 5-32: prepare_execute_arguments currently returns errors without
logging; update it to emit debug/tracing logs whenever it rejects arguments: log
the incoming tool and arguments and the error message before returning Err in
the object-type mismatch branch inside prepare_execute_arguments, and likewise
log validation failures just before propagating errors from
normalize_calendar_time_bounds, ensure_notion_fetch_type,
validate_gmail_send_email, and validate_gmail_add_label (include the tool name
and the specific validation error). Use the project's tracing/log macros at
debug or trace level and keep the logs concise to aid debugging.
- Around line 61-62: The current heuristic in the if branch that checks s.len()
and byte positions (s.len() == 10 && s.as_bytes().get(4) == Some(&b'-') &&
s.as_bytes().get(7) == Some(&b'-')) only validates ASCII layout and can accept
impossible dates; replace this pre-flight with an explicit date parse (e.g.,
using chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") or time::Date::parse) to
validate the YYYY-MM-DD value: if parsing succeeds, produce the normalized
RFC3339 midnight string (e.g., format "{s}T00:00:00Z" or build from the parsed
date) and otherwise fall back to the existing behavior (or return None/propagate
error) so invalid dates like "2026-99-99" are rejected earlier; update the block
that currently inspects s and bytes accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0293a95-1492-4b3f-b220-014e6b545cb3

📥 Commits

Reviewing files that changed from the base of the PR and between 21fd2cd and 711e27e.

📒 Files selected for processing (14)
  • app/src/lib/composio/formatters.test.ts
  • app/src/lib/composio/formatters.ts
  • src/openhuman/composio/action_tool.rs
  • src/openhuman/composio/client.rs
  • src/openhuman/composio/error_mapping.rs
  • src/openhuman/composio/error_mapping_tests.rs
  • src/openhuman/composio/execute_dispatch.rs
  • src/openhuman/composio/execute_dispatch_tests.rs
  • src/openhuman/composio/execute_prepare.rs
  • src/openhuman/composio/execute_prepare_tests.rs
  • src/openhuman/composio/mod.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/tools.rs
  • src/openhuman/socket/event_handlers.rs

Comment thread src/openhuman/composio/ops.rs Outdated
Comment thread src/openhuman/socket/event_handlers.rs Outdated
Comment on lines +35 to +40
let payload_json = data.to_string();
let preview_end = crate::openhuman::util::floor_char_boundary(&payload_json, 500);
log::debug!(
"[socket] event payload: name={} data={}",
event_name,
&data.to_string()[..data.to_string().len().min(500)]
&payload_json[..preview_end]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact payload content from debug logging.

Line 38 currently logs raw payload content (truncated), which can still leak secrets/PII for short events. Keep byte length and structural metadata, but omit raw body text.

Proposed fix
     let payload_json = data.to_string();
     let preview_end = crate::openhuman::util::floor_char_boundary(&payload_json, 500);
+    let payload_shape = match &data {
+        serde_json::Value::Object(map) => format!("object_keys={}", map.len()),
+        serde_json::Value::Array(arr) => format!("array_len={}", arr.len()),
+        serde_json::Value::String(_) => "string".to_string(),
+        serde_json::Value::Number(_) => "number".to_string(),
+        serde_json::Value::Bool(_) => "bool".to_string(),
+        serde_json::Value::Null => "null".to_string(),
+    };
     log::debug!(
-        "[socket] event payload: name={} data={}",
+        "[socket] event payload: name={} data_bytes={} preview_bytes={} shape={} preview_omitted=true",
         event_name,
-        &payload_json[..preview_end]
+        payload_json.len(),
+        preview_end,
+        payload_shape
     );

As per coding guidelines: “Never log secrets, raw JWTs, API keys, credentials, or full PII in debug logs; redact or omit sensitive fields.”

🤖 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/socket/event_handlers.rs` around lines 35 - 40, The debug log
currently prints raw, truncated payload text via payload_json and preview_end
(using crate::openhuman::util::floor_char_boundary), which can leak secrets;
change the logging to avoid raw body content and instead log safe metadata such
as the payload byte length and structural info: compute payload_len =
payload_json.len() (or data.as_bytes().len()), and in the log::debug call for
event_name replace &payload_json[..preview_end] with a redacted summary like
"<REDACTED_BODY length={payload_len} chars>" (or include inferred content
type/field keys if available) so the log shows size/structure but never raw body
text. Ensure you update any uses of preview_end/floor_char_boundary accordingly
to avoid slicing into the body when redacting.

- ops.rs: preserve `[composio:error:<class>] …` tag from the dispatcher so
  the frontend formatter in app/src/lib/composio/formatters.ts can parse
  the class. Updated ops_tests::composio_execute_via_mock_propagates_backend_error
  to assert the new classified format.
- error_mapping.rs: log classifier outcome once per call (tool + class)
  so the gateway-vs-provider decision is traceable.
- execute_dispatch_tests.rs: drop the broken oneshot graceful-shutdown
  plumbing (sender was dropped immediately, signalling shutdown before
  the test could exercise the server).
- execute_dispatch.rs: include the validation error message in the
  pre-flight rejection debug log; document why only SLACK_FETCH_CONVERSATION_HISTORY
  is allow-listed for transparent rate-limit retries.
- execute_prepare.rs: validate bare YYYY-MM-DD with chrono::NaiveDate
  so impossible dates like 2026-99-99 are rejected up front instead of
  being passed through to Google Calendar.
- client_tests.rs: fix execute_tool_surfaces_non_successful_provider_response
  to use a tool slug that bypasses local arg validation, so the test
  exercises the gateway-response path as intended.
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.

Actionable comments posted: 1

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

Inline comments:
In `@src/openhuman/composio/ops.rs`:
- Around line 268-275: Add branch-level debug tracing when mapping execute
errors in the error-handling block that checks
e.starts_with("[composio:error:"), so you log which branch was taken and include
the error payload; specifically, in the block inside ops.rs where the code
currently returns Err(e) or Err(format!("[composio] execute failed: {e}")), emit
a debug/trace call (use the project's tracing macro, e.g., tracing::debug!)
before returning — one message for the "classified" branch indicating a
preserved composio:error class and the error string, and another for the
"wrapped" branch indicating an execute failure and the wrapped message including
e.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0ec6fa6f-70c3-4e06-8d25-ebef2cb17699

📥 Commits

Reviewing files that changed from the base of the PR and between 711e27e and 8fb2690.

📒 Files selected for processing (7)
  • src/openhuman/composio/client_tests.rs
  • src/openhuman/composio/error_mapping.rs
  • src/openhuman/composio/execute_dispatch.rs
  • src/openhuman/composio/execute_dispatch_tests.rs
  • src/openhuman/composio/execute_prepare.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/ops_tests.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/openhuman/composio/execute_dispatch.rs
  • src/openhuman/composio/execute_prepare.rs
  • src/openhuman/composio/error_mapping.rs

Comment thread src/openhuman/composio/ops.rs
…sio-error-mapping

# Conflicts:
#	src/openhuman/composio/ops.rs
…oolError

Bring diff coverage for formatters.ts above the 80% gate by exercising the
empty-body fallback messages for each classified error case (validation,
insufficient_scope, rate_limited, gateway) plus the unknown-class default
and null/undefined/empty inputs.
# Conflicts:
#	src/openhuman/socket/event_handlers.rs
@senamakel senamakel self-assigned this May 16, 2026
senamakel added 3 commits May 15, 2026 20:12
# Conflicts:
#	src/openhuman/composio/action_tool.rs
#	src/openhuman/composio/mod.rs
#	src/openhuman/composio/ops.rs
#	src/openhuman/composio/tools.rs
- composio/ops.rs: emit branch-level tracing when mapping execute
  errors (classified vs wrapped) so incident triage can grep the
  decision (CodeRabbit #3250474827).
- socket/event_handlers.rs: stop logging raw payload bytes at debug
  level; emit byte-length + top-level shape only so PII / secrets /
  auth tokens in socket frames cannot leak through debug logs
  (CodeRabbit #3250222027).
- composio/tools.rs: rustfmt collapse of the `super::client` import
  group left over after dropping `direct_execute`.
# Conflicts:
#	src/openhuman/composio/client.rs
@senamakel senamakel merged commit 258837e into tinyhumansai:main May 16, 2026
19 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Composio tool errors are misclassified as 502s

2 participants