{"record":{"id":"d48960bc31d73e69","repo":"tinyhumansai/openhuman","slug":"artifact-list-e","errorCode":null,"errorMessage":"artifact_list: {e}","messagePattern":"artifact_list: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/agent/artifacts/tools.rs","lineNumber":91,"sourceCode":"            \"type\": \"object\",\n            \"properties\": {\n                \"offset\": { \"type\": \"integer\", \"minimum\": 0, \"description\": \"Pagination offset (default 0).\" },\n                \"limit\": { \"type\": \"integer\", \"minimum\": 1, \"description\": \"Max artifacts to return (default 50, cap 200).\" }\n            }\n        })\n    }\n\n    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {\n        log::debug!(\"[tool][artifacts] list invoked\");\n        let offset = read_opt_usize(&args, \"offset\");\n        let limit = read_opt_usize(&args, \"limit\");\n        // The agent-facing tool surface lists everything in the workspace;\n        // the per-chat filter is an RPC-only knob used by the React panel\n        // (#3226). Keep the tool path unchanged so existing agent flows\n        // (orchestrator artifact reasoning) still see the full set.\n        let outcome = ops::ai_list_artifacts(&self.config, offset, limit, None)\n            .await\n            .map_err(|e| anyhow::anyhow!(\"artifact_list: {e}\"))?;\n        Ok(ToolResult::success(serde_json::to_string(&outcome.value)?))\n    }\n\n    fn is_concurrency_safe(&self, _args: &serde_json::Value) -> bool {\n        true\n    }\n}\n\n/// Retrieve a single artifact's metadata plus its absolute on-disk path.\npub struct ArtifactGetTool {\n    config: Arc<Config>,\n}\n\nimpl ArtifactGetTool {\n    pub fn new(config: Arc<Config>) -> Self {\n        Self { config }\n    }\n}","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/openhuman/agent/artifacts/tools.rs#L73-L109","documentation":"Wrapper around `ops::ai_list_artifacts(&config, offset, limit, None)` failing in ArtifactListTool::execute. The chain goes store::list_artifacts → artifacts_root (create_dir_all on `<workspace>/artifacts/`), so failures are workspace-IO-shaped: the workspace dir cannot be created/read (permissions, path is a file, disk error). Note the tool intentionally passes thread_id=None to list the whole workspace (#3226) — filtering bugs are not the failure mode here.","triggerScenarios":"The agent invokes artifact_list while config.workspace_dir points at an unwritable/removable path, a path whose parent is a file, or a filesystem with an I/O error. An empty or missing artifacts dir normally yields a clean empty listing, not this error.","commonSituations":"Workspace on an unmounted external drive/network share; workspace_dir overridden via -w/--workspace or env to a bad path; sandboxed agent runtime denying FS access to the workspace; artifacts dir corrupted mid-scan.","solutions":["Check the inner `{e}` — it names the exact IO operation and path.","Confirm `config.workspace_dir` exists, is a directory, and is readable+writable by the core process.","Create the artifacts root manually (`mkdir -p <workspace>/artifacts`) to surface any permission error early.","If workspace_dir came from a flag/env, point it back at the real workspace.","Re-mount/repair the underlying filesystem if removable/network storage is involved."],"exampleFix":"# before: workspace_dir=/mnt/usb/openhuman (drive unmounted -> mkdir fails)\n# after\nls -ld /mnt/usb/openhuman && mkdir -p /mnt/usb/openhuman/artifacts\nopenhuman ...   # or set workspace_dir back to ~/.openhuman/users/<id>","handlingStrategy":"try-catch","validationCode":"// Verify the workspace is usable before invoking the tool:\nlet root = config.workspace_dir.join(\"artifacts\");\nstd::fs::create_dir_all(&root)?; // surfaces permission/path errors here, with a clear context","typeGuard":null,"tryCatchPattern":"match ArtifactListTool::new(config.clone()).execute(args).await {\n    Ok(res) => res,\n    Err(e) if e.to_string().starts_with(\"artifact_list:\") => {\n        // Inner error is IO-shaped; check workspace_dir mount/permissions,\n        // log and degrade gracefully (empty list) rather than failing the turn.\n        ToolResult::success(serde_json::to_string(&serde_json::json!({\"artifacts\": [], \"total\": 0}))?)\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep workspace_dir on reliable local storage.","Pre-create <workspace>/artifacts at deploy time to fail fast on permission problems.","Treat list failures as empty-state candidates in agent flows after logging."],"tags":["rust","agent-tools","artifacts","filesystem","io"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}