zeroclaw-labs/zeroclaw · error · anyhow::Error
Screenshot path '{ $path }' resolves to a non-UTF-8 pathname
Error message
Screenshot path '{ $path }' resolves to a non-UTF-8 pathname; refusing to write through a lossy conversion What it means
The allowlist validated the byte-preserving PathBuf, but every screenshot backend consumes the destination as a UTF-8 string. If the canonical target contains non-UTF-8 bytes (possible on Unix when a symlinked parent canonicalizes into a legacy-encoded directory), to_str() returns None and the tool refuses rather than applying to_string_lossy(), which would substitute U+FFFD and name a pathname that never passed policy.
Source
Thrown at crates/zeroclaw-tools/src/browser.rs:939
"tool-browser-screenshot-error-symlink-target",
&[("target", &resolved_target.display().to_string())],
);
anyhow::bail!("{msg}");
}
// The allowlist above validated the byte-preserving PathBuf. Every
// backend receives the destination as a UTF-8 string, and a lossy
// conversion (`to_string_lossy`) would silently replace non-UTF-8
// bytes with U+FFFD — naming a pathname that never passed the policy.
// Fail closed here, while we still hold the checked target: on Unix a
// valid UTF-8 input can canonicalize (through a symlink) to a parent
// containing non-UTF-8 bytes.
let Some(resolved_str) = resolved_target.to_str() else {
let msg = crate::i18n::get_required_tool_string_with_args(
"tool-browser-screenshot-error-path-not-utf8",
&[("path", raw_path)],
);
anyhow::bail!("{msg}");
};
Ok(resolved_str.to_string())
}
fn validate_computer_use_action(
&self,
action: &str,
params: &serde_json::Map<String, Value>,
) -> anyhow::Result<()> {
match action {
"open" => {
let url = params.get("url").and_then(Value::as_str).ok_or_else(|| {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
.with_outcome(::zeroclaw_log::EventOutcome::Failure),
"browser: Missing 'url' for open action"View on GitHub (pinned to 88bb9c8533)
Solutions
- Rename the non-UTF-8 directory component to a valid UTF-8 name (convmv or manual rename)
- Choose a screenshot destination inside a fully UTF-8 directory tree within the workspace
- Remove or retarget the symlink whose canonicalization introduces the non-UTF-8 bytes
Example fix
# before: the workspace parent resolves (via symlink) to a Latin-1 named directory
{"action": "screenshot", "path": "shot.png"}
# after: write inside a guaranteed UTF-8 directory
{"action": "screenshot", "path": "captures/shot.png"} Defensive patterns
Strategy: validation
Validate before calling
let canonical = tokio::fs::canonicalize(parent).await?;
let target = canonical.join(file_name);
if target.to_str().is_none() {
// canonical destination is not UTF-8; reject before invoking the tool
} Try / catch
match tool.execute(args).await {
Ok(res) => { /* ... */ }
Err(e) if e.to_string().contains("non-UTF-8") => {
// pick a destination inside a UTF-8 directory tree; do not retry as-is
}
Err(e) => return Err(e),
} Prevention
- Keep workspace paths pure ASCII
- Rename legacy-encoded directories before pointing tools at them
- Avoid symlinks whose targets lead outside a known UTF-8 tree
When it happens
Trigger: A syntactically valid UTF-8 path whose canonical parent directory contains non-UTF-8 bytes — typically reached through a symlink whose target is a Latin-1/ISO-8859-1 named directory, common in old home directories or migrated filesystems.
Common situations: Workspaces nested under legacy locale-encoded directories on older Linux/macOS setups; SMB/NFS mounts with mixed-encoding filenames; symlinks created by migration tooling pointing at byte-named targets.
Related errors
- Cannot write screenshot to runtime config path '{ $target }'
- Cannot write screenshot to symlink target '{ $target }'
- attachment path {} canonicalizes to {} which escapes workspa
- attachment path {} escapes workspace {}
- Key file path is a symlink — refusing to write
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/550e120ee8d078b7.
Report an issue: GitHub.