santifer/career-ops · error · Error

clone of @ failed

Error message

clone of ${url}@${sha.slice(0, 10)} failed — ${err.stderr ? String(err.stderr).slice(0, 200) : err.message}

What it means

safeClone wraps every git invocation (init, remote add, fetch --depth 1 of the pinned sha, checkout) and, on failure, deletes the temp dir and re-throws a message containing the URL, the first 10 sha chars, and up to 200 chars of git's stderr (or the error message). It turns low-level execFileSync failures into one actionable, rate-limit-aware message.

Solutions

  1. Read the embedded stderr segment — it names the concrete cause (auth, DNS, not-found, timeout)
  2. Verify the URL and sha exist: `git ls-remote <url> <sha>`
  3. Check network/proxy access to github.com and retry; on CI ensure git is installed
  4. If the sha was force-pushed away, pick a new commit and re-pin

Example fix

// before
const dir = safeClone('https://github.com/acme/career-ops-plugin-demo', sha); // throws opaque exec errors
// after
try {
  const dir = safeClone(url, sha);
} catch (e) {
  console.error(e.message); // 'clone of https://...@a1b2c3d4e5 failed — fatal: could not read Username...'
  // inspect stderr, check network, or verify the sha exists
}
Defensive patterns

Strategy: retry

Validate before calling

import { execFileSync } from 'child_process'; try { execFileSync('git', ['ls-remote', url, sha], { stdio: 'ignore' }); } catch { throw new Error(`unreachable repo or sha: ${url}@${sha}`); }

Try / catch

let dir; for (let attempt = 1; attempt <= 3 && !dir; attempt++) { try { dir = safeClone(url, sha); } catch (e) { if (attempt === 3 || /not found|could not read/i.test(e.message)) throw e; await sleep(2 ** attempt * 500); } }

Prevention

When it happens

Trigger: safeClone called when git is missing/not on PATH, the repo/SHA does not exist (fetch fails), the network is down or a proxy blocks github.com, GitHub is rate-limiting/unavailable, or the 120s timeout expires.

Common situations: Offline or corporate proxy blocks git; typo'd owner/repo after URL validation passed; pinned sha was garbage-collected or never fetched (force-pushed away); no SSH/network access from CI; git not installed in a slim container image.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/087a82e8a899cdba. Report an issue: GitHub.

Appendix: source

Thrown at plugin-install.mjs:54

  if (!m) throw new Error(`repo must be named "career-ops-plugin-<name>" (got "${repoName}")`);
  return { url, id: m[1] };
}

/** Clone the EXACT pinned SHA into a fresh temp dir. Returns the temp dir path. */
export function safeClone(url, sha) {
  if (!SHA_RE.test(sha || '')) throw new Error(`a 40-hex commit --sha is required (got ${JSON.stringify(sha)})`);
  const dir = mkdtempSync(path.join(tmpdir(), 'co-plugin-'));
  const git = (...args) => execFileSync('git', ['-c', 'protocol.ext.allow=never', '-c', 'protocol.file.allow=never', ...args], { stdio: ['ignore', 'ignore', 'pipe'], timeout: 120_000 });
  try {
    git('-C', dir, 'init', '-q');
    git('-C', dir, 'remote', 'add', 'origin', '--', url);
    git('-C', dir, 'fetch', '--depth', '1', '--no-tags', '-q', 'origin', sha);
    git('-C', dir, 'checkout', '-q', 'FETCH_HEAD');
    rmSync(path.join(dir, '.git'), { recursive: true, force: true }); // drop VCS metadata (and any hooks)
    return dir;
  } catch (err) {
    rmSync(dir, { recursive: true, force: true });
    throw new Error(`clone of ${url}@${sha.slice(0, 10)} failed — ${err.stderr ? String(err.stderr).slice(0, 200) : err.message}`);
  }
}

/** Check the minimum file set + a valid manifest whose id matches `expectId`. */
export function validateInstall(dir, expectId) {
  const problems = [];
  for (const f of MIN_FILES) if (!existsSync(path.join(dir, f))) problems.push(`missing required file: ${f}`);
  if (problems.length) return { ok: false, problems, manifest: null };
  let parsed;
  try { parsed = JSON.parse(readFileSync(path.join(dir, 'manifest.json'), 'utf8')); }
  catch (e) { return { ok: false, problems: [`manifest.json invalid JSON: ${e.message}`], manifest: null }; }
  // validateManifest wants the dir to BE the plugin dir + the basename to equal id.
  const tmpNamed = path.join(path.dirname(dir), expectId);
  if (dir !== tmpNamed) { try { renameSync(dir, tmpNamed); dir = tmpNamed; } catch { /* validate in place using expectId */ } }
  const manifest = validateManifest(parsed, dir, expectId);
  if (!manifest) return { ok: false, problems: ['manifest failed validation (see ⚠️ above)'], manifest: null, dir };
  const audit = auditPlugin(dir);
  if (!audit.ok) return { ok: false, problems: audit.findings.map(f => `${f.file}: ${f.issue}`), manifest, dir };

View on GitHub (pinned to aac998c7ed)