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
- Replace the character named in the message with an allowed one — in practice stick to letters, digits, '-', '_', '.', '~', and '/'
- Strip percent-encoding entirely: write `/zero-claw-gw`, not `/zero%2Dclaw` — '%' is invalid
- Move query strings and route placeholders out of path_prefix; they belong on individual routes
- 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
- Restrict prefixes to [A-Za-z0-9/-._~] — the extra sub-delims invite mistakes
- Never percent-encode path_prefix; '%' is rejected
- Pin the deployed prefix in a unit test that runs the same allowlist
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- cloud_ops.iac_tools must not be empty when cloud_ops is enab
- risk_profiles.{profile_alias}.shell_env_passthrough[{i}] is
- security.otp.cache_valid_secs must be greater than or equal
- security.otp.gated_actions[{i}] contains invalid characters:
- providers.models.{profile_name}.uri must use http/https
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/0d3ca39ce21d90f5.
Report an issue: GitHub.