tinyhumansai/openhuman · error · anyhow::Error

composio.authorize: {AUTHORIZE_OAUTH_SCOPES_FIELD} must be a

Error message

composio.authorize: {AUTHORIZE_OAUTH_SCOPES_FIELD} must be a string or array

What it means

The shape-level guard of merge_required_oauth_scopes: the oauth_scopes field must be a JSON string (comma/whitespace separated, which the client splits) or an array of strings. Any other JSON type — object, number, boolean — bails client-side before the authorize request is sent.

Source

Thrown at src/openhuman/integrations/composio/client.rs:640

            .map(ToString::to_string)
            .collect(),
        Value::Array(items) => {
            let mut out = Vec::with_capacity(items.len() + required.len());
            for item in items {
                let Some(scope) = item.as_str() else {
                    anyhow::bail!(
                        "composio.authorize: {AUTHORIZE_OAUTH_SCOPES_FIELD} entries must be strings"
                    );
                };
                let scope = scope.trim();
                if !scope.is_empty() {
                    out.push(scope.to_string());
                }
            }
            out
        }
        _ => {
            anyhow::bail!(
                "composio.authorize: {AUTHORIZE_OAUTH_SCOPES_FIELD} must be a string or array"
            );
        }
    };

    for scope in required {
        if !scopes.iter().any(|existing| existing == scope) {
            scopes.push((*scope).to_string());
        }
    }
    *value = json!(scopes);
    Ok(())
}

/// Backend-mode [`ComposioClient`] constructor. **Internal to the
/// composio module** — external callers should use
/// [`create_composio_client`] (factory) or
/// [`crate::openhuman::agent::harness::subagent_runner::user_is_signed_in_to_composio`]

View on GitHub (pinned to 7491200858)

Solutions

  1. Fix the producer to emit either a comma/space-separated string or an array of scope strings
  2. If you have an object-style scope map, flatten it to an array of granted scope names before calling
  3. Validate the oauth_scopes shape at config load time (null/string/array-of-strings only)

Example fix

// before
let extra = json!({ "oauth_scopes": {"repo": true} });
client.authorize("github", Some(extra)).await?;

// after — flatten a scope map into the accepted array form
let scopes: Vec<String> = scope_map
    .iter()
    .filter(|(_, v)| v.as_bool().unwrap_or(false))
    .map(|(k, _)| k.clone())
    .collect();
let extra = json!({ "oauth_scopes": scopes });
client.authorize("github", Some(extra)).await?;
Defensive patterns

Strategy: type-guard

Validate before calling

// Normalize any scope representation into the accepted string|array-of-strings shape
fn normalize_oauth_scopes(v: &serde_json::Value) -> serde_json::Value {
    match v {
        serde_json::Value::Null => serde_json::Value::Array(vec![]),
        serde_json::Value::String(_) => v.clone(),
        serde_json::Value::Array(_) => v.clone(),
        serde_json::Value::Object(map) => serde_json::Value::Array(
            map.iter()
                .filter(|(_, v)| v.as_bool().unwrap_or(false))
                .map(|(k, _)| serde_json::json!(k))
                .collect(),
        ),
        _ => serde_json::Value::Array(vec![]), // numbers/bools carry no scopes
    }
}

Type guard

fn oauth_scopes_shape_ok(v: &serde_json::Value) -> bool {
    matches!(v, serde_json::Value::Null | serde_json::Value::String(_))
        || v.as_array().is_some_and(|a| a.iter().all(|i| i.is_string()))
}

Prevention

When it happens

Trigger: Calling authorize with oauth_scopes set to a non-string, non-array value, e.g. {"oauth_scopes": 3}, {"oauth_scopes": true}, or {"oauth_scopes": {"read": true}}.

Common situations: Config schema loosened so any JSON is accepted for the field; a count or flag accidentally stored under the scopes key; object-style scope maps ("read": true) forwarded where a list is expected.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/060122b0a30c71e3. Report an issue: GitHub.