{"record":{"id":"26d1404ba3f39593","repo":"jackwener/OpenCLI","slug":"stale-git-index-lock-found-remove-it-first","errorCode":null,"errorMessage":"Stale .git/index.lock found — remove it first","messagePattern":"Stale \\.git/index\\.lock found — remove it first","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"autoresearch/engine.ts","lineNumber":98,"sourceCode":"  }\n\n  private log(msg: string): void {\n    this.callbacks.onStatus?.(msg);\n  }\n\n  /** Phase 0: Precondition checks */\n  private checkPreconditions(): void {\n    // Git repo exists\n    try { execStrict('git rev-parse --git-dir'); }\n    catch { throw new Error('Not a git repository'); }\n\n    // Clean working tree\n    const status = exec('git status --porcelain');\n    if (status) throw new Error(`Working tree not clean:\\n${status}`);\n\n    // No stale locks\n    if (existsSync(join(ROOT, '.git', 'index.lock'))) {\n      throw new Error('Stale .git/index.lock found — remove it first');\n    }\n\n    // Not detached HEAD\n    try { execStrict('git symbolic-ref HEAD'); }\n    catch { throw new Error('Detached HEAD — checkout a branch first'); }\n  }\n\n  /** Phase 5: Run verify command and extract metric */\n  private runVerify(): number | null {\n    this.log('  verify...');\n    const output = exec(this.config.verify, { timeout: 300_000 });\n    return extractMetric(output);\n  }\n\n  /** Phase 5.5: Run guard command */\n  private runGuard(): boolean {\n    if (!this.config.guard) return true;\n    this.log('  guard...');","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/autoresearch/engine.ts#L80-L116","documentation":"toEnvelope is the CLI's central error-to-envelope mapper in src/errors.ts. Known CliError instances keep their own code/message; anything else falls into this UNKNOWN branch at line 277, where the envelope's message is just whatever getErrorMessage(err) recovered (e.g. err.message or String(err)) and the exit code is EXIT_CODES.GENERIC_ERROR. Hitting this means a non-CliError exception (a raw TypeError, network error, JSON.parse failure, etc.) escaped a command without being wrapped.","triggerScenarios":"Any command path that lets a plain Error (or non-Error value) reach the top-level envelope() call instead of throwing a CliError — e.g. unhandled TypeError inside a command, JSON.parse failure, uncaught promise rejection from a dependency.","commonSituations":"A library call throws a native error the CLI forgot to wrap; a bug (undefined property access) surfaces in production; an async callback rejects outside the CliError-wrapping layer; a non-Error value like a string is thrown.","solutions":["Read the envelope's message (and optional cause/trace fields) to identify the underlying raw exception, then fix that root cause.","Wrap known failure modes in CliError with a specific code so users get actionable codes instead of UNKNOWN.","Use the trace fields (traceId/summaryPath) if present to inspect what the CLI was doing when the raw error escaped.","If the message is empty or 'undefined', the thrown value wasn't an Error — add logging of the raw thrown value in the command path."],"exampleFix":"// before\nconst data = JSON.parse(raw); // raw TypeError escapes as UNKNOWN\n// after\nlet data;\ntry {\n  data = JSON.parse(raw);\n} catch (err) {\n  throw new CliError('PARSE_ERROR', `Invalid JSON: ${getErrorMessage(err)}`);\n}","handlingStrategy":"try-catch","validationCode":"if (err instanceof CliError) {\n  // known, coded failure — envelope preserves err.code\n} else {\n  // will surface as code 'UNKNOWN'; log raw value now before mapping\n  console.error('raw thrown value:', typeof err, err);\n}","typeGuard":"function isCliError(err: unknown): err is CliError {\n  return err instanceof CliError;\n}","tryCatchPattern":"try {\n  await runCommand(argv);\n} catch (err) {\n  const envelope = toEnvelope(err);\n  if (envelope.error.code === 'UNKNOWN') {\n    logger.debug({ raw: err }, 'unwrapped error surfaced as UNKNOWN');\n  }\n  process.exitCode = envelope.error.exitCode;\n  console.error(JSON.stringify(envelope, null, 2));\n}","preventionTips":["Wrap every anticipated failure mode in CliError with a specific code at the layer where it occurs.","Never throw raw strings or non-Error values; always throw Error subclasses so messages survive getErrorMessage.","Keep the cause chain (new Error(msg, { cause })) so the envelope's cause field aids debugging.","Review any UNKNOWN envelope in CI logs as a signal that a command path is missing error wrapping."],"tags":["error-handling","cli","unknown-error","envelope"],"backgroundTag":"unhandled-unknown-error","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}