{"record":{"id":"a4a3a9d4e4f3ad4e","repo":"nexu-io/open-design","slug":"git-command-failed","errorCode":null,"errorMessage":"git command failed","messagePattern":"git command failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/live-artifacts/refresh.ts","lineNumber":621,"sourceCode":"    parsed = JSON.parse(await readFile(targetReal, 'utf8')) as BoundedJsonValue;\n  } catch {\n    throw new Error(`project_files.read_json could not parse JSON at ${filePath}`);\n  }\n  return asBoundedRefreshOutput({ toolName: 'project_files.read_json', path: filePath, size: entryStat.size, json: parsed });\n}\n\nfunction compactExecOutput(value: string): string[] {\n  return value.split('\\n').map((line) => line.trimEnd()).filter(Boolean).slice(0, 100);\n}\n\nasync function runGit(projectPath: string, args: string[], signal: AbortSignal | undefined): Promise<string> {\n  try {\n    const result = await execFileAsync('git', args, { cwd: projectPath, signal, timeout: 10_000, maxBuffer: 128 * 1024 });\n    return result.stdout.toString();\n  } catch (error) {\n    const maybeError = error as { stdout?: string | Buffer; stderr?: string | Buffer; message?: string; code?: unknown };\n    if (maybeError.code === 128) return '';\n    throw new Error(maybeError.stderr?.toString().trim() || maybeError.message || 'git command failed');\n  }\n}\n\nasync function executeGitSummary(options: ExecuteLocalDaemonRefreshSourceOptions): Promise<BoundedJsonObject> {\n  const input = options.source.input as GitSummaryInput;\n  const maxCommits = optionalPositiveInteger(input.maxCommits, 'input.maxCommits', 10, 50);\n  const dir = projectDir(options.projectsRoot, options.projectId);\n  const insideWorkTree = (await runGit(dir, ['rev-parse', '--is-inside-work-tree'], options.signal)).trim() === 'true';\n  if (!insideWorkTree) return asBoundedRefreshOutput({ toolName: 'git.summary', isRepository: false, branch: '', status: [], recentCommits: [], diffStat: [] });\n\n  const [branch, status, recentCommits, diffStat] = await Promise.all([\n    runGit(dir, ['branch', '--show-current'], options.signal),\n    runGit(dir, ['status', '--short'], options.signal),\n    runGit(dir, ['log', `--max-count=${maxCommits}`, '--pretty=format:%h %s'], options.signal),\n    runGit(dir, ['diff', '--stat', '--', '.'], options.signal),\n  ]);\n\n  return asBoundedRefreshOutput({","sourceCodeStart":603,"sourceCodeEnd":639,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/live-artifacts/refresh.ts#L603-L639","documentation":"runGit (refresh.ts:610-622) runs `git` via execFileAsync with a 10s timeout and 128 KiB buffer. Exit code 128 is treated as the benign 'not a git repo / expected git error' case and returns ''. Any other failure surfaces stderr (trimmed), then the error message, then the literal fallback 'git command failed'.","triggerScenarios":"git fails with a code other than 128 AND stderr/message are empty/missing — e.g. git is not installed (ENOENT), the process is killed by the timeout, the buffer overflows, or git crashes without writing to stderr.","commonSituations":"git binary missing from PATH (packaged/minimal environments, containers); a git operation exceeding the 10s timeout on a huge repo; output exceeding 128 KiB; permission errors reading .git; signal/abort during refresh.","solutions":["Ensure git is installed and on PATH for the daemon process (`git --version`).","For slow repos, reduce git.summary scope (lower maxCommits) or run refresh when the repo is less busy.","If git output exceeds the buffer, trim the repo history or avoid git.summary on very large repos.","Check the refresh log for the actual stderr/message when present; this literal message only appears when both are empty."],"exampleFix":"// before: git missing from the daemon's PATH\n// runGit throws `git command failed`\n\n// after: ensure git is resolvable\n// export PATH=\"$PATH:/usr/bin\"  (or install git in the container)\n// verify: node -e \"require('child_process').execFile('git',['--version'],(e,o)=>console.log(e,o))\"","handlingStrategy":"try-catch","validationCode":"import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nconst execFileAsync = promisify(execFile);\nasync function gitAvailable(): Promise<boolean> {\n  try {\n    await execFileAsync('git', ['--version']);\n    return true;\n  } catch {\n    return false;\n  }\n}\nif (!await gitAvailable()) throw new Error('git not found on PATH');","typeGuard":null,"tryCatchPattern":"try {\n  await runGit(dir, ['rev-parse', '--is-inside-work-tree'], signal);\n} catch (err) {\n  const e = err as { code?: number; stderr?: string };\n  if (e.code === 128) return; // benign: not a git repo\n  if (!e.stderr) {\n    // git missing / timeout / buffer overflow -> 'git command failed'\n  }\n  throw err;\n}","preventionTips":["Ensure the git binary is installed and on the daemon's PATH (verify with `git --version` from the daemon env).","Keep git repos shallow so summary commands stay under the 10s timeout and 128 KiB buffer.","Log stderr alongside the fallback message so 'git command failed' is diagnosable."],"tags":["live-artifacts","git","refresh","subprocess"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}