zeroclaw-labs/zeroclaw · error

gateway.path_prefix contains invalid character '{bad}'; only

Error message

gateway.path_prefix contains invalid character '{bad}'; only unreserved and sub-delim URI characters are allowed

What it means

The gateway mount prefix must consist solely of RFC 3986 unreserved characters (alphanumerics and -._~), the sub-delims !$&'()*+,;=, the path-safe delims : and @, plus /. Validation scans the raw prefix and bails naming the first offending character. Percent-encoding is deliberately unsupported — '%' is itself rejected — so the prefix must be written literally.

Source

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

                        "gateway.path_prefix must start with '/'"
                    );
                }
                if prefix.ends_with('/') {
                    validation_bail!(
                        InvalidFormat,
                        "gateway.path_prefix",
                        "gateway.path_prefix must not end with '/' (including bare '/')"
                    );
                }
                // Reject characters unsafe for URL paths or HTML/JS injection.
                // Whitespace is intentionally excluded from the allowed set.
                if let Some(bad) = prefix.chars().find(|c| {
                    !matches!(c, '/' | '-' | '_' | '.' | '~'
                        | 'a'..='z' | 'A'..='Z' | '0'..='9'
                        | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '='
                        | ':' | '@')
                }) {
                    anyhow::bail!(
                        "gateway.path_prefix contains invalid character '{bad}'; \
                         only unreserved and sub-delim URI characters are allowed"
                    );
                }
            }
        }

        // Skill bundles — directories must stay inside `<install>/shared/`
        // and no two bundles may resolve to the same directory. Default
        // directory and the rules themselves live in
        // [`crate::skill_bundles`] so the runtime SkillsService and this
        // validator share one implementation.
        if !self.skill_bundles.is_empty() {
            let install_root = self.install_root_dir();
            for alias in self.skill_bundles.keys() {
                let dir = crate::skill_bundles::resolve_directory(self, &install_root, alias)
                    .map_err(|e| {
                        ::zeroclaw_log::record!(

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Replace the character named in the message with an allowed one — in practice stick to letters, digits, '-', '_', '.', '~', and '/'
  2. Strip percent-encoding entirely: write `/zero-claw-gw`, not `/zero%2Dclaw` — '%' is invalid
  3. Move query strings and route placeholders out of path_prefix; they belong on individual routes
  4. Re-run validation after the edit to catch any second bad character (only the first is reported)

Example fix

# before
[gateway]
path_prefix = "/ze ro claw%20gw/{team}"

# after
[gateway]
path_prefix = "/zero-claw-gw"
Defensive patterns

Strategy: validation

Validate before calling

fn valid_path_prefix(prefix: &str) -> bool {
    prefix.chars().all(|c| matches!(c,
        '/' | '-' | '_' | '.' | '~'
        | 'a'..='z' | 'A'..='Z' | '0'..='9'
        | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '='
        | ':' | '@'))
}

// call before Config::validate():
// assert!(valid_path_prefix(&cfg.gateway.path_prefix));

Type guard

fn is_safe_gateway_prefix(p: &str) -> bool {
    !p.chars().any(|c| matches!(c, '%' | '?' | '#' | '[' | ']' | '<' | '>' | '"' | '\\' | '|' | '^' | '{' | '}' ) || !c.is_ascii())
}

Try / catch

if let Err(err) = config.validate() {
    if err.to_string().contains("gateway.path_prefix contains invalid character") {
        // re-read the char after 'invalid character' from the message and strip/replace it
    }
}

Prevention

When it happens

Trigger: Set gateway.path_prefix to any value containing a disallowed character: a space, '%', '?', '#', '[', ']', '<', '>', '|', '^', '"', backslash, braces, or any non-ASCII character. The bail fires during gateway config validation with the bad character interpolated into the message.

Common situations: Pasting a percent-encoded URL segment (`/bot%20gw`); including a query string or template placeholder (`/gw?v=2`, `/gw/{team}`); glob/route-template syntax leaking into the mount prefix; non-ASCII separators inserted by editors or IMEs.

Understand the failure class

Related errors


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