zeroclaw-labs/zeroclaw · error

google_workspace.allowed_operations

Error message

google_workspace.allowed_operations[{i}].resource contains invalid characters: {resource}

What it means

Config::validate() checks that allowed_operations[].resource contains only ASCII alphanumeric characters (both cases), '_' and '-'. Unlike service IDs, camelCase is deliberately accepted here because Google API resource names are camelCase (calendarList, quickUpdate); spaces, dots, slashes and other punctuation are rejected so the resource can be matched unambiguously against runtime tool identifiers.

Solutions

  1. Use the bare camelCase resource name from the Google API ("calendarList", "files", "messages")
  2. Replace spaces with '_' or '-' if you need a separator, or drop path prefixes entirely
  3. Verify the value against the service's API surface so the runtime check can actually match

Example fix

# before
[[google_workspace.allowed_operations]]
service = "calendar"
resource = "calendar/v3/calendarList"
methods = ["list"]

# after
[[google_workspace.allowed_operations]]
service = "calendar"
resource = "calendarList"
methods = ["list"]
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_gws_resource(s: &str) -> bool {
    !s.trim().is_empty()
        && s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
}

Type guard

fn is_valid_gws_resource(s: &str) -> bool {
    s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
}

Try / catch

match config.validate() {
    Ok(()) => {}
    Err(e) if e.to_string().contains("resource contains invalid characters") => {
        // strip path segments/dots; keep the bare camelCase resource name
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: An operation entry with resource = "calendar list", resource = "files.attachments", or resource = "a/b" in the config file, surfaced when Config::validate() runs at startup or config check.

Common situations: Transcribing REST paths ("calendar/v3/calendarList") or dot-separated names from API documentation instead of the bare resource name; inserting spaces while hand-editing long TOML arrays.

Understand the failure class

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/15ea4dd3456a46ce. Report an issue: GitHub.

Appendix: source

Thrown at crates/zeroclaw-config/src/schema.rs:21873

                     effective allowed_services; this entry can never match at runtime"
                );
            }
            if !service
                .chars()
                .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')
            {
                anyhow::bail!(
                    "google_workspace.allowed_operations[{i}].service contains invalid characters: {service}"
                );
            }
            // Unlike service IDs, resource/sub_resource/method names are camelCase
            // in the Google APIs (calendarList, quickAdd, batchUpdate), so
            // uppercase must be accepted here and in the runtime tool check.
            if !resource
                .chars()
                .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
            {
                anyhow::bail!(
                    "google_workspace.allowed_operations[{i}].resource contains invalid characters: {resource}"
                );
            }

            if let Some(ref sub_resource) = operation.sub_resource {
                let sub = sub_resource.trim();
                if sub.is_empty() {
                    anyhow::bail!(
                        "google_workspace.allowed_operations[{i}].sub_resource must not be empty when present"
                    );
                }
                if !sub
                    .chars()
                    .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
                {
                    anyhow::bail!(
                        "google_workspace.allowed_operations[{i}].sub_resource contains invalid characters: {sub}"
                    );

View on GitHub (pinned to 88bb9c8533)