tinyhumansai/openhuman · error · anyhow::Error
artifact_delete: {e}
Error message
artifact_delete: {e} What it means
Wrapper around `ops::ai_delete_artifact` failing in ArtifactDeleteTool::execute. Like get, the inner error is usually get_artifact's "artifact not found or unreadable" (delete resolves the meta first) or an IO failure removing `<workspace>/artifacts/<id>/`. The tool is PermissionLevel::Dangerous and default-OFF; the error means nothing was deleted — the irreversible action did not run.
Source
Thrown at src/openhuman/agent/artifacts/tools.rs:192
json!({
"type": "object",
"properties": {
"artifact_id": { "type": "string", "description": "The artifact id (UUID) to delete." }
},
"required": ["artifact_id"]
})
}
fn permission_level(&self) -> PermissionLevel {
PermissionLevel::Dangerous
}
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
log::debug!("[tool][artifacts] delete invoked");
let id = read_required_str(&args, "artifact_id")?;
let outcome = ops::ai_delete_artifact(&self.config, &id)
.await
.map_err(|e| anyhow::anyhow!("artifact_delete: {e}"))?;
Ok(ToolResult::success(serde_json::to_string(&outcome.value)?))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::openhuman::tools::traits::ToolScope;
fn test_config() -> Arc<Config> {
Arc::new(Config::default())
}
#[test]
fn metadata_is_stable() {
let cfg = test_config();
assert_eq!(ArtifactListTool::new(cfg.clone()).name(), "artifact_list");
assert_eq!(ArtifactGetTool::new(cfg.clone()).name(), "artifact_get");View on GitHub (pinned to a221052e0d)
Solutions
- List artifacts first and delete an id that exists in the current workspace.
- Treat 'not found' on a re-delete as success — the end state (absent) is already achieved.
- Verify write permission on `<workspace>/artifacts/<id>/` and the parent.
- Keep the Dangerous permission gate enabled so deletions route through human approval.
Example fix
// before: blind delete
{ "artifact_id": makeUpAnId() }
// after: resolve from a live listing
const list = await artifactList();
const target = list.artifacts.find(a => a.title === "Q3 Deck");
if (target) await artifactDelete(target.id); Defensive patterns
Strategy: validation
Validate before calling
// Mandatory pre-check before an irreversible delete:
let meta = ops::ai_get_artifact(&config, &id).await?; // fails here if absent
log::info!("[audit] deleting artifact {} ({}) from {}", meta.id, meta.title, meta.workspace_dir);
ops::ai_delete_artifact(&config, &id).await?; Type guard
fn is_plausible_artifact_id(s: &str) -> bool {
s.len() == 36 && s.matches('-').count() == 4
} Try / catch
match ops::ai_delete_artifact(&config, &id).await {
Ok(()) => Ok(()),
Err(e) if e.to_string().contains("not found or unreadable") => {
// End state already achieved (absent) — treat as success, log it.
Ok(())
}
Err(e) => Err(anyhow!("artifact_delete: {e}")),
} Prevention
- Keep the Dangerous permission level and its approval gate enabled for this tool.
- Resolve via get before delete; never delete by a model-guessed id.
- Idempotency rule: a second delete failing 'not found' means success.
When it happens
Trigger: Agent invokes artifact_delete (when the tool is enabled) with a stale/unknown artifact_id, or with an id from a different workspace; the rm of the artifact dir fails on permissions or a read-only mount.
Common situations: Double-delete (second call with an already-removed id); workspace switched between sessions; artifacts dir on read-only storage; id copy-paste error from the model.
Related errors
- artifact_get: {e}
- missing required string argument `{key}`
- artifact_list: {e}
- {tool}: facet not found: {fk}
- toolkit `{toolkit}` is not connected. Connected toolkits: [{
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/35d00f876ff89c31.
Report an issue: GitHub.