jdx/mise · error

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

Error message

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

What it means

ManagedFileRequest::from_toml pattern-matches on (source, content, state) and the (Some, Some, _) arm bails: a `[bootstrap.files]` entry cannot declare both `source` (path to a file whose contents to copy, resolved relative to the config file when relative) and `content` (inline string) (managed_files.rs:447-452). mise cannot know which one wins, so it rejects the ambiguity at parse time, before any templating.

Source

Thrown at src/system/managed_files.rs:442

        );
    }
    Ok(())
}

impl ManagedFileRequest {
    fn from_toml(
        root_config: &Config,
        path: PathBuf,
        config: ManagedFileTomlConfig,
        base: &Path,
        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 = Path::new(&source);
                let source = if source.is_absolute() {
                    source.to_path_buf()
                } else {
                    base.join(source)
                };
                Some(fs::read_to_string(&source).wrap_err_with(|| {
                    format!(
                        "[bootstrap.files].\"{}\": failed to read source {}",
                        path.display(),
                        source.display()
                    )
                })?)

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Keep exactly one of source or content: use source for shared/template files on disk, content for short inline strings
  2. If the entry is state = "absent", remove both keys entirely (see error 610)
  3. After editing, re-run the bootstrap plan to confirm the entry parses

Example fix

# before
[bootstrap.files."/etc/motd"]
source = "motd.tmpl"
content = "welcome"

# after
[bootstrap.files."/etc/motd"]
content = "welcome"
Defensive patterns

Strategy: validation

Validate before calling

for (path, f) in bootstrap_files(config) {
    assert!(!(f.source.is_some() && f.content.is_some()),
        "{path}: source and content are mutually exclusive");
}

Type guard

// Config struct invariant, checked before handing off to mise
fn is_valid_file_entry(f: &ManagedFileTomlConfig) -> bool {
    !(f.source.is_some() && f.content.is_some())
}

Prevention

When it happens

Trigger: A single file entry like `[bootstrap.files."/etc/motd"]` with both `source = "motd.tmpl"` and `content = "hello"` in mise.toml. The check fires for any state, including absent (where the (Some,Some,_) arm precedes the absent-specific arm, so both-fields-plus-absent reports this message first).

Common situations: Iterating on a file's content inline (content = ...) and forgetting to delete an earlier source = ...; merging config snippets from docs/examples that each used a different mechanism; absent-state entries left with stale content/source keys after switching state.

Related errors


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