{"record":{"id":"ac5085e6b5b8f06a","repo":"JuliusBrussee/caveman","slug":"base-and-head-must-be-full-git-object-ids","errorCode":null,"errorMessage":"base and head must be full Git object IDs","messagePattern":"base and head must be full Git object IDs","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"agents/check-profile-scope.mjs","lineNumber":46,"sourceCode":"}\n\nfunction requireGit(result, operation) {\n  if (!result.error && result.status === 0) return result.stdout;\n  const detail = result.stderr?.toString().trim() || result.error?.message || `exit ${result.status}`;\n  throw new Error(`${operation} failed: ${detail}`);\n}\n\nfunction isConcreteProfile(path) {\n  return path !== SCHEMA_PATH && /^agents\\/profiles\\/[a-z0-9][a-z0-9-]*\\.json$/.test(path);\n}\n\nfunction isAllowedConcreteProfileCommitPath(path) {\n  return isConcreteProfile(path) || GENERATED_PATHS.has(path);\n}\n\nexport function checkProfileScope({ base, head, cwd = process.cwd() }) {\n  if (!REVISION_RE.test(base) || !REVISION_RE.test(head)) {\n    throw new Error(\"base and head must be full Git object IDs\");\n  }\n\n  const baseRegistry = runGit(cwd, [\"cat-file\", \"-e\", `${base}:agents/agents.json`]);\n  if (baseRegistry.error || baseRegistry.status !== 0) {\n    return { ok: true, skipped: true, message: \"profile registry absent at base (initial import); skipping lane-scope gate\" };\n  }\n\n  const commitsText = requireGit(runGit(cwd, [\"rev-list\", \"--reverse\", `${base}..${head}`]), \"git rev-list\");\n  const commits = commitsText.split(/\\r?\\n/).filter(Boolean);\n  let contractTouched = false;\n\n  for (const commit of commits) {\n    const changedBuffer = requireGit(\n      // -m takes the union across merge parents; otherwise a merge commit can hide\n      // an out-of-scope path from an ordinary single-parent diff-tree view.\n      runGit(cwd, [\"diff-tree\", \"--root\", \"-m\", \"--no-commit-id\", \"--name-only\", \"-r\", \"-z\", commit], null),\n      `git diff-tree ${commit}`,\n    );","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/agents/check-profile-scope.mjs#L28-L64","documentation":"checkProfileScope requires base and head to be full Git object IDs (40-char hex SHAs) validated by REVISION_RE, and throws 'base and head must be full Git object IDs' otherwise. The gate deliberately rejects symbolic refs, short SHAs, and branch names so its cat-file/diff plumbing commands operate on unambiguous commits.","triggerScenarios":"Calling checkProfileScope({ base, head }) with branch names ('main'), short SHAs ('abc123'), ranges ('main...head'), annotated ref names, or refs like 'HEAD~1'.","commonSituations":"CI systems exporting branch names instead of commit SHAs into base/head variables, GitHub Actions events providing refs rather than SHAs, or hand-testing with abbreviated hashes.","solutions":["Resolve refs to full SHAs before calling: git rev-parse <ref>^{commit}","In CI, use the event's commit SHA variables (e.g. GITHUB_SHA, or actions/checkout's fetch-depth + rev-parse) instead of branch names","Reject/normalize short SHAs by expanding them with git rev-parse","Validate input length (40 hex chars, 64 for SHA-256 repos) before invoking the gate"],"exampleFix":"// before\ncheckProfileScope({ base: 'main', head: 'HEAD' })\n// after\nconst base = exec('git rev-parse main^{commit}').trim();\nconst head = exec('git rev-parse HEAD^{commit}').trim();\ncheckProfileScope({ base, head })","handlingStrategy":"validation","validationCode":"const REVISION_RE = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/;\nif (!REVISION_RE.test(base) || !REVISION_RE.test(head)) {\n  throw new Error(`base/head must be full object IDs, got base=${base} head=${head}`);\n}","typeGuard":"const isFullOid = (s) => typeof s === 'string' && /^[0-9a-f]{40}$|^[0-9a-f]{64}$/.test(s);","tryCatchPattern":"try { checkProfileScope({ base, head }); } catch (e) {\n  if (e.message.includes('full Git object IDs')) {\n    base = revParse(base); head = revParse(head); // resolve refs to SHAs and retry\n    return checkProfileScope({ base, head });\n  } throw e;\n}","preventionTips":["Always git rev-parse refs to full SHAs before invoking the gate","In CI use commit-SHA event variables, not branch names","Never pass ranges, short SHAs, or HEAD~n forms","Add a pre-call assertion with a clear error naming the offending value"],"tags":["git","validation","sha","cli"],"backgroundTag":"invalid-argument-format","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}