jdx/mise · error

invalid or unenrolled permission path {path}

Error message

invalid or unenrolled permission path {path}

What it means

Each entry in the manifest's permissions map must have mode bits no greater than 0o777, a path accepted by is_safe_branch_path, and the path must correspond to an enrolled stream via owns_stream. This error rejects permission entries that are malformed or reference paths not enrolled in the manifest.

Source

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

            }
            let mut variants = std::collections::BTreeSet::new();
            super::select::validate(&entry.variants)?;
            for variant in &entry.variants {
                let name = variant.name();
                if name.contains('@')
                    || !super::sync::layout::is_safe_branch_path(&name)
                    || !variants.insert(name)
                {
                    bail!("invalid or repeated variant for {}", entry.path);
                }
            }
        }
        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");
        }
        #[derive(Deserialize)]
        struct FormatHeader {
            format: u64,
        }
        let bytes = repo.cat_object_bounded(&oid, 4 * 1024 * 1024)?;
        let header: FormatHeader = serde_json::from_slice(&bytes)?;

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Ensure the permissions path exactly matches an enrolled stream path (enroll the stream first).
  2. Clamp permission bits to 0o777 (drop setuid/setgid/sticky bits).
  3. Fix the path so it passes is_safe_branch_path (no traversal, relative, safe chars).
  4. Remove the stale permissions entry if the stream was intentionally unenrolled.

Example fix

// before (manifest JSON)
{"permissions": {"dotfiles/vimrc": 0o4755}}
// after
{"permissions": {"dotfiles/vimrc": 0o755}}
Defensive patterns

Strategy: validation

Validate before calling

let bits_ok = bits & !0o777 == 0;
let enrolled = manifest.streams.iter().any(|s| s.path == path);
if !(bits_ok && enrolled) { return Err(format!("bad permission entry: {path}")); }

Type guard

fn valid_permission(path: &str, bits: u32) -> bool { bits <= 0o777 && !path.contains("..") }

Try / catch

if let Err(e) = manifest.validate() {
    if e.to_string().contains("invalid or unenrolled permission path") {
        // enroll the stream or drop the permission entry
    }
}

Prevention

When it happens

Trigger: Calling validate on a manifest where a permissions key has bits > 0o777, is an unsafe branch path, or names a path for which owns_stream returns false (no matching stream enrollment).

Common situations: Setting permission bits with extra flag bits (setuid/setgid/sticky) in the manifest; adding permissions for a dotfile before enrolling its stream; typos in the path so it doesn't match an enrolled stream.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/4c4e59a7d904e325. Report an issue: GitHub.