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

  1. List artifacts first and delete an id that exists in the current workspace.
  2. Treat 'not found' on a re-delete as success — the end state (absent) is already achieved.
  3. Verify write permission on `<workspace>/artifacts/<id>/` and the parent.
  4. 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

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


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/35d00f876ff89c31. Report an issue: GitHub.