tinyhumansai/openhuman · error
learning_save_profile: create dir failed: {e}
Error message
learning_save_profile: create dir failed: {e} What it means
Thrown by learning_save_profile when tokio::fs::create_dir_all() on the parent of {workspace_dir}/PROFILE.md fails. Practically that means creating the workspace directory itself; create_dir_all fails on permission denial, a path component existing as a regular file, a read-only filesystem, or I/O errors. Display-only {e} carries the std::io::Error.
Source
Thrown at src/openhuman/agent/learning/tools.rs:544
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let config = config_rpc::load_config_with_timeout()
.await
.map_err(|e| anyhow::anyhow!("learning_save_profile: {e}"))?;
let body = if summarize {
crate::openhuman::agent::learning::linkedin_enrichment::summarise_profile_with_llm(
&config, &markdown,
)
.await
.map_err(|e| anyhow::anyhow!("learning_save_profile: summarisation failed: {e:#}"))?
} else {
markdown
};
let path = config.workspace_dir.join("PROFILE.md");
if let Some(parent) = path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| anyhow::anyhow!("learning_save_profile: create dir failed: {e}"))?;
}
tokio::fs::write(&path, &body)
.await
.map_err(|e| anyhow::anyhow!("learning_save_profile: write failed: {e}"))?;
Ok(ToolResult::success(serde_json::to_string(&json!({
"path": path.display().to_string(),
"bytes": body.len(),
}))?))
}
}
/// Enrich the profile via LinkedIn (external scrape). Default-OFF.
pub struct LearningEnrichProfileTool;
#[async_trait]
impl Tool for LearningEnrichProfileTool {
fn name(&self) -> &str {
"learning_enrich_profile"View on GitHub (pinned to a221052e0d)
Solutions
- Check the path in the error: ensure every component is a directory the process can write (ls -ld, fix ownership/permissions)
- Remove/rename any regular file squatting on a needed directory component
- Verify the volume is mounted and not read-only
- Confirm the config's workspace_dir setting / env override points where you expect
Defensive patterns
Strategy: try-catch
Validate before calling
// In-process check the tool's own precondition: let cfg = config_rpc::load_config_with_timeout().await?; let dir = cfg.workspace_dir.clone(); std::fs::create_dir_all(&dir)?; // surface EACCES/EEXIST-as-file before invoking the tool
Try / catch
match tool_exec("learning_save_profile", args).await {
Err(e) if e.to_string().contains("create dir failed") => {
// inspect {e}: EACCES -> fix ownership; EEXIST/ENOTDIR -> a file occupies a path component
Err(e)
}
other => other,
} Prevention
- Keep workspace_dir on a writable local volume owned by the running user
- Never let a regular file shadow a directory component of the workspace path
- Don't run the core as root and then as a user against the same workspace — ownership flips break creates
When it happens
Trigger: workspace_dir resolving to a protected path (permissions changed, different user owning the dir); a stale file occupying a directory component of the path (e.g. 'workspace' existing as a file); read-only mount; workspace on an unmounted/removed volume.
Common situations: Workspace directory deleted manually while the core runs; migrating machines with copied home dirs that lost ownership; OPENHUMAN_WORKSPACE override pointing somewhere unwritable.
Related errors
- learning_save_profile: write failed: {e}
- artifact_list: {e}
- artifact_get: {e}
- artifact_delete: {e}
- learning_update_facet: {e:#}
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/12259cbc417c6546.
Report an issue: GitHub.