{"record":{"id":"c6747d6ff3981b87","repo":"jdx/mise","slug":"invalid-pinned-revision","errorCode":null,"errorMessage":"invalid pinned revision","messagePattern":"invalid pinned revision","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/remote_repository.rs","lineNumber":191,"sourceCode":"        update,\n        yes,\n        dry_run,\n        &global_directory(),\n    )\n}\n\nfn install_at(\n    bundle: &Path,\n    origin: &str,\n    revision: &str,\n    update: bool,\n    yes: bool,\n    dry_run: bool,\n    destination: &Path,\n) -> Result<PathBuf> {\n    validate_origin(origin)?;\n    if !matches!(revision.len(), 40 | 64) || !revision.bytes().all(|b| b.is_ascii_hexdigit()) {\n        bail!(\"invalid pinned revision\");\n    }\n    if destination.is_symlink() {\n        bail!(\"global configuration directory must not be a symlink\");\n    }\n    let parent = destination\n        .parent()\n        .ok_or_else(|| eyre::eyre!(\"missing parent directory\"))?;\n    if !dry_run {\n        std::fs::create_dir_all(parent)?;\n    }\n    let _lock = crate::lock_file::LockFile::new(destination).lock()?;\n    // the checkout is renamed into place, so it is staged next to the\n    // destination; a dry run never renames and leaves the parent alone\n    let temporary = if dry_run {\n        tempfile::tempdir()?\n    } else {\n        tempfile::tempdir_in(parent)?\n    };","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/remote_repository.rs#L173-L209","documentation":"install_at requires the revision to be a full git object hash: exactly 40 hex chars (SHA-1) or 64 hex chars (SHA-256). Anything else — branch names, short SHAs, tags — is rejected so the install is always pinned to an immutable commit.","triggerScenarios":"Calling install_at (or install / install_source / preview_source) with a revision like \"main\", \"v1.2.3\", or a 7-char short SHA.","commonSituations":"Passing a branch or tag name where a pinned commit is expected, or a truncated SHA copied from a log viewer.","solutions":["Resolve the ref to a full hash first: git rev-parse main","Use the complete 40- or 64-character commit hash","Do not pass tags or branch names to install_at"],"exampleFix":"// before\ninstall_at(origin, \"abc1234\", ...);\n// after\nlet rev = git_rev_parse(\"abc1234\"); // full 40-hex hash\ninstall_at(origin, &rev, ...);","handlingStrategy":"validation","validationCode":"fn is_pinned_revision(rev: &str) -> bool {\n    matches!(rev.len(), 40 | 64) && rev.bytes().all(|b| b.is_ascii_hexdigit())\n}","typeGuard":"fn full_sha(input: &str) -> Option<String> {\n    let ok = matches!(input.len(), 40 | 64)\n        && input.bytes().all(|b| b.is_ascii_hexdigit());\n    if ok { Some(input.to_lowercase()) } else { None }\n}","tryCatchPattern":null,"preventionTips":["Always resolve refs to full hashes with `git rev-parse` before pinning","Never pass branches, tags, or short SHAs to install_at","Store pins as full commit hashes in config/tooling"],"tags":["git","validation","revision"],"backgroundTag":"invalid-argument-format","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}