{"record":{"id":"348ad911987e2de4","repo":"jdx/mise","slug":"dotfile-enrollment-metadata-must-be-a-regular-file","errorCode":null,"errorMessage":"dotfile enrollment metadata must be a regular file","messagePattern":"dotfile enrollment metadata must be a regular file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/manifest.rs","lineNumber":398,"sourceCode":"            }\n        }\n        for (path, bits) in &self.permissions {\n            if *bits > 0o777\n                || !super::sync::layout::is_safe_branch_path(path)\n                || !self.owns_stream(path)\n            {\n                bail!(\"invalid or unenrolled permission path {path}\");\n            }\n        }\n        Ok(())\n    }\n\n    pub(crate) fn read(repo: &HistoryRepo, tree: &str) -> Result<Option<Self>> {\n        let Some((mode, oid)) = repo.object_at(tree, PATH)? else {\n            return Ok(None);\n        };\n        if mode != \"100644\" {\n            bail!(\"dotfile enrollment metadata must be a regular file\");\n        }\n        #[derive(Deserialize)]\n        struct FormatHeader {\n            format: u64,\n        }\n        let bytes = repo.cat_object_bounded(&oid, 4 * 1024 * 1024)?;\n        let header: FormatHeader = serde_json::from_slice(&bytes)?;\n        if header.format != 1 {\n            bail!(\"unsupported dotfile repository format {}\", header.format);\n        }\n        let manifest: Self = serde_json::from_slice(&bytes)?;\n        manifest.validate()?;\n        Ok(Some(manifest))\n    }\n\n    pub(crate) fn write(&self, repo: &HistoryRepo, tree: &str) -> Result<String> {\n        self.validate()?;\n        let oid = repo.hash_blob(&serde_json::to_vec_pretty(self)?)?;","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/manifest.rs#L380-L416","documentation":"Manifest::read looks up the enrollment metadata object at a fixed PATH in the history tree and requires it to be stored with git mode 100644 (regular, non-executable file). Any other mode (executable blob, symlink, tree, etc.) is rejected because the manifest must be a plain readable file.","triggerScenarios":"Reading the manifest from a history tree where the object at PATH was committed with mode 100755, 120000 (symlink), or 40000 (tree) instead of 100644.","commonSituations":"Manually rebuilding the history tree with git plumbing (hash-object/mktree) using the wrong mode; a symlinked manifest file committed as a symlink; corruption or tampering with the dotfile store.","solutions":["Re-commit the metadata object at PATH with mode 100644 (e.g. git hash-object + update-index with the correct mode).","If the file is a symlink, replace it with a real file and re-write the tree.","Restore the store from a known-good backup or re-run bootstrap enrollment to rebuild the tree."],"exampleFix":"// before: manifest committed as executable\n$ git update-index --add --cacheinfo 100755,<oid>,enrollment.json\n// after\n$ git update-index --add --cacheinfo 100644,<oid>,enrollment.json","handlingStrategy":"try-catch","validationCode":"// inspect the tree entry mode before reading\n// git ls-tree <tree> <PATH>  -> mode should be 100644","typeGuard":null,"tryCatchPattern":"match Manifest::read(repo, tree) {\n    Err(e) if e.to_string().contains(\"must be a regular file\") => {\n        eprintln!(\"dotfile store metadata corrupted; re-run bootstrap\");\n    }\n    other => other?,\n}","preventionTips":["Only modify the history tree with mise's own tooling, not raw git plumbing.","Never commit the manifest as a symlink or executable.","Back up the dotfile store before manual surgery."],"tags":["dotfiles","manifest","git","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}