jdx/mise · error

bootstrap compose project

Error message

bootstrap compose project '{name}' project_dir must be absolute: {}

What it means

A bootstrap compose declaration's project_dir must be an absolute filesystem path because mise resolves and compares compose project locations globally. Relative paths are ambiguous (depends on cwd at apply time), so from_toml_with_origin rejects them and echoes the offending path.

Solutions

  1. Change project_dir to an absolute path (start with / on Unix, drive letter on Windows)
  2. If using a variable, ensure it expands to an absolute path or anchor it, e.g. "{{ env.HOME }}/srv/app" or an absolute ${HOME}/...
  3. Verify with `mise bootstrap plan` that the path resolves as intended

Example fix

# before
[bootstrap.compose.app]
project_dir = "./app"

# after
[bootstrap.compose.app]
project_dir = "/home/me/src/app"
Defensive patterns

Strategy: validation

Validate before calling

import os
def validate_project_dir(config: dict) -> None:
    for name, decl in config.get("bootstrap", {}).get("compose", {}).items():
        d = decl.get("project_dir", "")
        if not os.path.isabs(os.path.expandvars(d)):
            raise ValueError(f"{name}: project_dir must be absolute: {d}")

Prevention

When it happens

Trigger: Config sets project_dir = "./compose" or "app/compose" under [bootstrap.compose.<name>]; a variable used in project_dir expands to a relative or empty path; programmatic construction passes a relative PathBuf.

Common situations: Hand-written configs using shorthand relative paths; env-var interpolation like project_dir = "${WORK_DIR}/app" where WORK_DIR is unset or relative; configs copied between machines with different layouts.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/58ee0d9237268398. Report an issue: GitHub.

Appendix: source

Thrown at src/system/compose.rs:318

        .unwrap_or(action)
}

impl ComposeRequest {
    #[cfg(test)]
    fn from_toml(name: String, config: ComposeTomlConfig) -> Result<Self> {
        Self::from_toml_with_origin(name, config, None)
    }

    fn from_toml_with_origin(
        name: String,
        config: ComposeTomlConfig,
        origin: Option<ResourceOrigin>,
    ) -> Result<Self> {
        if name.is_empty() {
            bail!("bootstrap compose project names cannot be empty");
        }
        if !config.project_dir.is_absolute() {
            bail!(
                "bootstrap compose project '{name}' project_dir must be absolute: {}",
                config.project_dir.display()
            );
        }
        validate_project_name(config.project_name.as_deref())?;
        validate_values("profile", &config.profiles)?;
        validate_values("service", &config.services)?;
        validate_values("oneshot service", &config.oneshot)?;
        validate_command("command", &config.command)?;
        validate_command("engine_command", &config.engine_command)?;
        if config.state == ComposeState::Absent && !config.services.is_empty() {
            bail!(
                "bootstrap compose project '{name}' services cannot be combined with state = \"absent\" because compose down removes the entire project"
            );
        }
        if !config.wait && config.wait_timeout.is_some() {
            bail!("bootstrap compose project '{name}' wait_timeout requires wait = true");
        }

View on GitHub (pinned to 533346cc37)