{"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/533346cc374382b41ec5ff70536252b2e96e725c/src/system/remote_repository.rs#L173-L209","documentation":"install_at only accepts a full commit SHA: the revision must be exactly 40 (SHA-1) or 64 (SHA-256) ASCII hex characters. Anything else — branch names, tags, short SHAs, refs like HEAD — is rejected with this error before any network or filesystem work.","triggerScenarios":"Calling install / install_at / install_source / preview_source with revision values like `main`, `v1.2.3`, `abc123` (short SHA), or a SHA containing non-hex characters.","commonSituations":"Users passing a tag where a commit SHA is required; truncating a SHA when copying from CI logs; using a branch name assuming refs are supported.","solutions":["Resolve the full SHA first: `git rev-parse <ref>` on the source repo, then pass the 40- or 64-char result.","If you only have a tag/branch, fetch the repository (Source::fetch) and resolve HEAD, or run `git ls-remote <origin> <ref>`.","Check for stray whitespace or `origin/` prefixes in the revision string."],"exampleFix":"// before\ninstall_at(\"https://github.com/org/repo.git\", \"v1.2.0\", ...).await?;\n// after\nlet rev = \"9fceb02d0ae598e95dc970b74767f19372d61af8\"; // git ls-remote origin v1.2.0\ninstall_at(\"https://github.com/org/repo.git\", rev, ...).await?;","handlingStrategy":"validation","validationCode":"function isFullSha(rev: string): boolean {\n  return (rev.length === 40 || rev.length === 64) && /^[0-9a-fA-F]+$/.test(rev);\n}\nif (!isFullSha(revision)) throw new Error(\"resolve ref to full SHA first: git rev-parse <ref>\");","typeGuard":"const isSha = (s: string): s is string => (s.length === 40 || s.length === 64) && /^[0-9a-f]{40}$|^[0-9a-f]{64}$/.test(s);","tryCatchPattern":null,"preventionTips":["Always resolve tags/branches to full SHAs (`git rev-parse` / `git ls-remote`) before install_at.","Store full SHAs in config; trim whitespace programmatically.","Never pass short SHAs or branch names."],"tags":["git","validation","revision","sha"],"backgroundTag":"invalid-argument-format","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}