jdx/mise · error

dotfile enrollment metadata must be a regular file

Error message

dotfile enrollment metadata must be a regular file

What it means

The enrollment metadata object inside the repository tree must be stored as a regular blob with git mode 100644. `read` bails when the object exists at the manifest path but has a different mode (e.g. a symlink, submodule, or executable), because the manifest loader can only safely parse a regular file's contents.

Solutions

  1. Fix the git mode of the metadata file to 100644 and commit: `git add --chmod=644 <manifest-path>`
  2. Remove any symlink/submodule at the manifest path and replace it with a regular file containing the manifest contents
  3. Inspect the tree entry mode (`git ls-tree <tree> <path>`) to confirm what object occupies the manifest path, then repair it

Example fix

// before
$ git ls-tree HEAD .history-manifest
100755 blob ... .history-manifest
// after
git add --chmod=644 .history-manifest && git commit -m "fix manifest mode"
Defensive patterns

Strategy: validation

Validate before calling

let (mode, _oid) = repo.object_at(tree, PATH)
  .ok_or("manifest missing")?;
if mode != "100644" { return Err(format!("manifest mode is {mode}, want 100644")); }

Try / catch

match Manifest::read(&repo, &tree) {
  Err(e) if e.to_string() == "dotfile enrollment metadata must be a regular file" => {
    // repair git mode to 100644 and retry
  }
  other => other,
}

Prevention

When it happens

Trigger: Calling `Manifest::read` against a tree where the manifest path (PATH) resolves to a non-100644 entry — e.g. the metadata file was committed as executable (100755), replaced by a symlink (120000), or turned into a gitlink (160000/submodule).

Common situations: Someone `chmod +x`'d the manifest file before committing; the manifest path was replaced by a symlink; a merge or import tool committed the metadata with the wrong mode; CI writes the manifest via a script that changes its mode.

Related errors


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

Appendix: source

Thrown at src/system/history/manifest.rs:398

            }
        }
        for (path, bits) in &self.permissions {
            if *bits > 0o777
                || !super::sync::layout::is_safe_branch_path(path)
                || !self.owns_stream(path)
            {
                bail!("invalid or unenrolled permission path {path}");
            }
        }
        Ok(())
    }

    pub(crate) fn read(repo: &HistoryRepo, tree: &str) -> Result<Option<Self>> {
        let Some((mode, oid)) = repo.object_at(tree, PATH)? else {
            return Ok(None);
        };
        if mode != "100644" {
            bail!("dotfile enrollment metadata must be a regular file");
        }
        let bytes = repo.cat_object_bounded(&oid, 4 * 1024 * 1024)?;
        Self::from_bytes(&bytes).map(Some)
    }

    pub(crate) fn read_gix(tree: &gix::Tree<'_>) -> Result<Option<Self>> {
        let Some(entry) = tree.lookup_entry_by_path(PATH)? else {
            return Ok(None);
        };
        if entry.mode().value() != 0o100644 {
            bail!("dotfile enrollment metadata must be a regular file");
        }
        if entry.id().header()?.size() > 4 * 1024 * 1024 {
            bail!("dotfile enrollment metadata is too large");
        }
        let object = entry.object()?;
        Self::from_bytes(&object.data).map(Some)
    }

View on GitHub (pinned to 533346cc37)