{"record":{"id":"35d00f876ff89c31","repo":"tinyhumansai/openhuman","slug":"artifact-delete-e","errorCode":null,"errorMessage":"artifact_delete: {e}","messagePattern":"artifact_delete: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/agent/artifacts/tools.rs","lineNumber":192,"sourceCode":"        json!({\n            \"type\": \"object\",\n            \"properties\": {\n                \"artifact_id\": { \"type\": \"string\", \"description\": \"The artifact id (UUID) to delete.\" }\n            },\n            \"required\": [\"artifact_id\"]\n        })\n    }\n\n    fn permission_level(&self) -> PermissionLevel {\n        PermissionLevel::Dangerous\n    }\n\n    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {\n        log::debug!(\"[tool][artifacts] delete invoked\");\n        let id = read_required_str(&args, \"artifact_id\")?;\n        let outcome = ops::ai_delete_artifact(&self.config, &id)\n            .await\n            .map_err(|e| anyhow::anyhow!(\"artifact_delete: {e}\"))?;\n        Ok(ToolResult::success(serde_json::to_string(&outcome.value)?))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::openhuman::tools::traits::ToolScope;\n\n    fn test_config() -> Arc<Config> {\n        Arc::new(Config::default())\n    }\n\n    #[test]\n    fn metadata_is_stable() {\n        let cfg = test_config();\n        assert_eq!(ArtifactListTool::new(cfg.clone()).name(), \"artifact_list\");\n        assert_eq!(ArtifactGetTool::new(cfg.clone()).name(), \"artifact_get\");","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/openhuman/agent/artifacts/tools.rs#L174-L210","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: blind delete\n{ \"artifact_id\": makeUpAnId() }\n// after: resolve from a live listing\nconst list = await artifactList();\nconst target = list.artifacts.find(a => a.title === \"Q3 Deck\");\nif (target) await artifactDelete(target.id);","handlingStrategy":"validation","validationCode":"// Mandatory pre-check before an irreversible delete:\nlet meta = ops::ai_get_artifact(&config, &id).await?; // fails here if absent\nlog::info!(\"[audit] deleting artifact {} ({}) from {}\", meta.id, meta.title, meta.workspace_dir);\nops::ai_delete_artifact(&config, &id).await?;","typeGuard":"fn is_plausible_artifact_id(s: &str) -> bool {\n    s.len() == 36 && s.matches('-').count() == 4\n}","tryCatchPattern":"match ops::ai_delete_artifact(&config, &id).await {\n    Ok(()) => Ok(()),\n    Err(e) if e.to_string().contains(\"not found or unreadable\") => {\n        // End state already achieved (absent) — treat as success, log it.\n        Ok(())\n    }\n    Err(e) => Err(anyhow!(\"artifact_delete: {e}\")),\n}","preventionTips":["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."],"tags":["rust","agent-tools","artifacts","not-found","destructive","permissions"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}