zeroclaw-labs/zeroclaw · error · anyhow::Error

Cannot write screenshot to runtime config path '{ $target }'

Error message

Cannot write screenshot to runtime config path '{ $target }'

What it means

The canonical screenshot-destination validator resolves the requested path against the workspace (tilde and `..` expanded, parent canonicalized) and then applies the same runtime-config guard used by file_write/file_edit: a target that is the workspace's config.toml, config.toml.bak, or a `.config.toml.tmp-*` temp file is refused. This stops a screenshot write from overwriting the agent's own live configuration. The error is raised before any backend writes a byte.

Source

Thrown at crates/zeroclaw-tools/src/browser.rs:913

        // the same target-level guards the file_write / file_edit tools use.
        let Some(file_name) = full.file_name() else {
            let msg = crate::i18n::get_required_tool_string_with_args(
                "tool-browser-screenshot-error-missing-filename",
                &[("path", raw_path)],
            );
            anyhow::bail!("{msg}");
        };
        let resolved_target = canonical.join(file_name);

        if self.security.is_runtime_config_path(&resolved_target) {
            let msg = crate::i18n::get_required_tool_string_with_args(
                "tool-browser-screenshot-error-runtime-config-target",
                &[
                    ("path", raw_path),
                    ("target", &resolved_target.display().to_string()),
                ],
            );
            anyhow::bail!("{msg}");
        }

        // If the target already exists and is a symlink, refuse to follow it.
        if let Ok(meta) = tokio::fs::symlink_metadata(&resolved_target).await
            && meta.file_type().is_symlink()
        {
            let msg = crate::i18n::get_required_tool_string_with_args(
                "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

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Pick a different destination filename, e.g. screenshots/capture-01.png
  2. If the goal is to change configuration, use the dedicated config-editing path (file_edit tool or config workflow), not a screenshot write
  3. Read the {target} field in the message to see the exact resolved path that tripped the guard, then remove the offending segment (`..`, tilde, or symlink) from the requested path

Example fix

// before
{"action": "screenshot", "path": "config.toml"}
// after
{"action": "screenshot", "path": "screenshots/capture-01.png"}
Defensive patterns

Strategy: validation

Validate before calling

fn is_runtime_config_name(name: &str) -> bool {
    name == "config.toml"
        || name == "config.toml.bak"
        || name.starts_with(".config.toml.tmp-")
}

let file = std::path::Path::new(&requested_path)
    .file_name().and_then(|n| n.to_str()).unwrap_or("");
if is_runtime_config_name(file) { /* choose another destination before calling */ }

Try / catch

match tool.execute(args).await {
    Ok(res) => { /* ... */ }
    Err(e) if e.to_string().contains("runtime config") => {
        // policy denial: do NOT retry the same path; pick a new filename
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Invoking the browser tool with action="screenshot" and a path whose resolved target lands on the runtime config set: path="config.toml", path="config.toml.bak", path=".config.toml.tmp-123", or a relative/tilde path that canonicalizes onto one of these after parent resolution.

Common situations: An LLM agent told to 'save a screenshot here' picks the most prominent file in the workspace root (config.toml); users assume every workspace path is writable by every tool; paths that look harmless but resolve through `..` or tilde expansion onto the config file.

Related errors


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