jdx/mise · error · eyre::Report

[bootstrap.files]." ": source and content are mutually…

Error message

[bootstrap.files]."{}": source and content are mutually exclusive

What it means

ManagedFileTomlConfig::try_from-style conversion (from_toml) validates each [bootstrap.files] entry. A managed file may take its bytes either from a source path on disk or from inline content, never both — the converter bails when config.source and config.content are both Some. This keeps provenance unambiguous for rendering and diffing.

Solutions

  1. Delete the `content` key and keep `source` if the file should be copied/rendered from disk.
  2. Delete the `source` key and keep `content` for inline file bodies.
  3. Move one of the two definitions into a separate file entry under a different path if both variants are genuinely needed.

Example fix

# before
[bootstrap.files."/etc/motd"]
source = "files/motd"
content = "Welcome"
state = "present"

# after
[bootstrap.files."/etc/motd"]
source = "files/motd"
state = "present"
Defensive patterns

Strategy: validation

Validate before calling

// rust
pub struct FileEntry { source: Option<String>, content: Option<String>, state: State }
impl FileEntry {
    fn validate(&self) -> Result<(), &'static str> {
        if self.source.is_some() && self.content.is_some() {
            return Err("source and content are mutually exclusive");
        }
        Ok(())
    }
}

Prevention

When it happens

Trigger: Building a ManagedFile config for a path where both `source` and `content` keys are present in the TOML table, with any state value.

Common situations: Migrating a file from inline content to a source template by adding source before deleting content; an editor merge that kept both keys; generated TOML emitting both fields unconditionally.

Related errors


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

Appendix: source

Thrown at src/system/managed_files.rs:596

    }
    Ok(())
}

impl ManagedFileRequest {
    fn from_toml(
        root_config: &Config,
        path: PathBuf,
        config: ManagedFileTomlConfig,
        base: &Path,
        origin: ResourceOrigin,
        secrets: &super::secrets::SecretValues,
    ) -> Result<Self> {
        let owner = nonempty("owner", config.owner)?;
        let group = nonempty("group", config.group)?;
        let mode = parse_mode(config.mode.as_deref(), 0o644)?;
        let mut content = match (config.source, config.content, config.state) {
            (Some(_), Some(_), _) => {
                bail!(
                    "[bootstrap.files].\"{}\": source and content are mutually exclusive",
                    path.display()
                )
            }
            (Some(source), None, ManagedState::Present) => {
                let source = resolve_source_path(base, &source);
                Some(fs::read_to_string(&source).wrap_err_with(|| {
                    format!(
                        "[bootstrap.files].\"{}\": failed to read source {}",
                        path.display(),
                        source.display()
                    )
                })?)
            }
            (None, Some(content), ManagedState::Present) => Some(content),
            (None, None, ManagedState::Present) => bail!(
                "[bootstrap.files].\"{}\": present files require source or content",
                path.display()

View on GitHub (pinned to 533346cc37)