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

  1. Rename the non-UTF-8 directory component to a valid UTF-8 name (convmv or manual rename)
  2. Choose a screenshot destination inside a fully UTF-8 directory tree within the workspace
  3. 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

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


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