gitbutlerapp/gitbutler · error · anyhow::Error

Could not determine home directory

Error message

Could not determine home directory

What it means

collect_skill_installs resolves the base directory for global skill installs via but_path::home_dir(), which returns None only when the underlying home-directory lookup fails (it first honors E2E_TEST_APP_DATA_DIR, then delegates to dirs::home_dir()). On Unix this happens when $HOME is unset or empty; on Windows when the user profile APIs yield nothing. Without a home directory there is nowhere to place ~/.agents-style global skills, so planning fails fast rather than writing to a guessed path.

Source

Thrown at crates/but/src/command/agent/plan.rs:271

}

#[derive(Debug, Clone)]
pub(super) struct InstructionWritePlan {
    pub(super) path: PathBuf,
    pub(super) agents: Vec<AgentTarget>,
}

pub(super) fn collect_skill_installs(
    agents: &[AgentTarget],
    scope: Scope,
    repo: Option<&RepoInfo>,
) -> Result<Vec<SkillInstallPlan>> {
    // Resolve each concrete install location (a single-location scope) to its
    // base directory once, expanding `Both` into global + repository.
    let mut locations: Vec<(Scope, PathBuf)> = Vec::new();
    if matches!(scope, Scope::Global | Scope::Both) {
        let home = but_path::home_dir()
            .ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
        locations.push((Scope::Global, home));
    }
    if matches!(scope, Scope::Repository | Scope::Both) {
        let root = repo
            .map(|repo| repo.root.clone())
            .context("Repository skill install requested outside a repository")?;
        locations.push((Scope::Repository, root));
    }

    let mut installs = Vec::new();
    for agent in agents {
        for (location, base_dir) in &locations {
            if let Some(components) = agent.skill_path_components(*location) {
                installs.push(SkillInstallPlan {
                    agent: *agent,
                    path: join_components(base_dir, components),
                });
            }

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run the command with HOME set: `HOME=$HOME but ...` in the failing context, or add `Environment=HOME=/root` / `SetEnv HOME=` to the service/cron definition.
  2. For automation and e2e runs, set E2E_TEST_APP_DATA_DIR — home_dir() then returns <dir>/home deterministically.
  3. If using sudo, preserve HOME (`sudo -E`) or set `env_keep+=HOME` in sudoers.
  4. If no global install is intended, scope the install to the repository (Scope::Repository) which uses the repo root instead.

Example fix

# before: cron job with no HOME
0 * * * * but agent skill install --global my-skill
# -> Error: Could not determine home directory

# after: provide HOME explicitly
0 * * * * HOME=/home/user but agent skill install --global my-skill
Defensive patterns

Strategy: validation

Validate before calling

// Check before planning a global skill install
match but_path::home_dir() {
    Some(home) => { /* safe to install globally into {home} */ }
    None => {
        eprintln!("HOME is not set; set HOME or E2E_TEST_APP_DATA_DIR, or use --repo scope");
        std::process::exit(2);
    }
}

Prevention

When it happens

Trigger: Running an agent skill-install command with Scope::Global or Scope::Both in a shell where HOME is unset/empty (cron jobs, bare systemd units, docker containers run without env, `env -i`), or in a test harness that neither sets E2E_TEST_APP_DATA_DIR nor provides a home directory.

Common situations: CI containers (alpine/slim images) that strip environment variables; launchd/systemd services without Environment=HOME=; running as a UID without a passwd entry; misconfigured sudo that drops HOME (`sudo but ...` with env_reset).

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/1f8e8f1e74dedba8. Report an issue: GitHub.