{"record":{"id":"c268e7df500cf67d","repo":"vercel-labs/skills","slug":"buildgithubautherror-url-repo-errormessage","errorCode":null,"errorMessage":"buildGitHubAuthError(url, repo, errorMessage)","messagePattern":"buildGitHubAuthError\\(url, repo, errorMessage\\)","errorType":"exception","errorClass":"GitCloneError","httpStatus":null,"severity":"error","filePath":"src/git.ts","lineNumber":294,"sourceCode":"      }\n\n      try {\n        await resetTempDir(tempDir);\n        await createGitClient(process.env.GIT_SSH_COMMAND ?? 'ssh -o BatchMode=yes').clone(\n          repo.sshUrl,\n          tempDir,\n          cloneOptions\n        );\n        return tempDir;\n      } catch {\n        // Fall through to the targeted auth error below.\n      }\n    }\n\n    await rm(tempDir, { recursive: true, force: true }).catch(() => {});\n\n    if (isAuthError) {\n      throw new GitCloneError(buildGitHubAuthError(url, repo, errorMessage), url, false, true);\n    }\n\n    throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);\n  }\n}\n\n/**\n * Resolve the Git tree object for a locked skill path in a cloned repository.\n * This matches the folder SHA returned by GitHub's Trees API.\n */\nexport async function getGitTreeHash(repoDir: string, skillPath: string): Promise<string | null> {\n  const normalizedPath = skillPath.replace(/\\\\/g, '/');\n  const segments = normalizedPath.split('/');\n  segments.pop();\n  const folderPath = segments.join('/');\n  const revision = folderPath ? `HEAD:${folderPath}` : 'HEAD^{tree}';\n\n  try {","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/git.ts#L276-L312","documentation":"When a clone fails and the error text matches auth-failure heuristics (isAuthFailure), cloneRepo throws a GitCloneError whose message is built by buildGitHubAuthError — a detailed guide covering SSH key and HTTPS/gh credential setup for private repositories.","triggerScenarios":"Cloning a private GitHub repo with missing/invalid credentials: no SSH key loaded (Permission denied (publickey)), expired gh token, revoked PAT, or HTTPS 403/Authentication failed output from git.","commonSituations":"Fresh machines without gh auth login; SSH keys not added to the agent; organization SSO tokens that expired; CI jobs missing the GITHUB_TOKEN/GH_TOKEN secret.","solutions":["For HTTPS: run gh auth login (or export GH_TOKEN/GITHUB_TOKEN with repo scope)","For SSH: verify ssh-add -l lists a key and ssh -T git@github.com authenticates","If using a PAT, confirm it is not expired and has access to the specific private org/repo (SSO authorized)","Alternatively clone where credentials already work and 'skills add <local-path>'"],"exampleFix":"# before\nskills add github.com/org/private-skills   # auth failure\n# after\ngh auth login\nskills add github.com/org/private-skills","handlingStrategy":"fallback","validationCode":"async function canClone(url: string): Promise<boolean> {\n  try {\n    execSync(`git ls-remote ${JSON.stringify(url)} HEAD`, { stdio: 'ignore', timeout: 20000 });\n    return true;\n  } catch { return false; }\n}","typeGuard":"function isGitAuthError(e: unknown): e is Error {\n  return e instanceof Error && /Permission to|authentication|credentials|gh auth|publickey/i.test(e.message);\n}","tryCatchPattern":"try { await cloneRepo(url); }\ncatch (e) {\n  if (isGitAuthError(e)) {\n    notifyUser('Run `gh auth login` or load an SSH key, then retry');\n    return; // user-actionable, don't crash the process\n  }\n  throw e;\n}","preventionTips":["Run gh auth login (or export GH_TOKEN) in CI before skill installs","Verify ssh -T git@github.com works for SSH workflows","Keep tokens fresh; org SSO re-authorization expires silently"],"tags":["git","authentication","github","private-repo"],"backgroundTag":"git-authentication-failed","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}