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 aView on GitHub (pinned to 88bb9c8533)
Solutions
- Pick a different destination filename, e.g. screenshots/capture-01.png
- If the goal is to change configuration, use the dedicated config-editing path (file_edit tool or config workflow), not a screenshot write
- 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
- Default screenshot destinations to a dedicated screenshots/ subdirectory
- Treat path-policy errors as non-retryable — the identical path always fails again
- Never point output-writing tools at workspace config files
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
- Cannot write screenshot to symlink target '{ $target }'
- Screenshot path '{ $path }' resolves to a non-UTF-8 pathname
- purge_session not supported by this memory backend
- Lark/Feishu marker target uses a disallowed scheme
- Lark/Feishu marker target resolves outside workspace_dir
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/eb8f12d2c1136ebb.
Report an issue: GitHub.