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

  1. Check the path in the error: ensure every component is a directory the process can write (ls -ld, fix ownership/permissions)
  2. Remove/rename any regular file squatting on a needed directory component
  3. Verify the volume is mounted and not read-only
  4. 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

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


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