{"record":{"id":"bb48f57ecdeb4a66","repo":"KeygraphHQ/shannon","slug":"git-checkpoint-failed-bb48f5","errorCode":"GIT_CHECKPOINT_FAILED","errorMessage":"Empty commit hash in array","messagePattern":"Empty commit hash in array","errorType":"exception","errorClass":"PentestError","httpStatus":null,"severity":"error","filePath":"apps/worker/src/temporal/activities.ts","lineNumber":1060,"sourceCode":"        `Resume scope mismatch for workspace ${input.sessionId}.\\n` +\n          `  Original: vuln_classes=[${recorded.vulnClasses.join(', ')}], exploit=${recorded.exploit}\\n` +\n          `  Provided: vuln_classes=[${vulnClasses.join(', ')}], exploit=${exploit}\\n` +\n          `Resume requires the same scope as the original run. Start a new workspace if you want different scope.`,\n        'ScopeMismatchError',\n      );\n    }\n    return;\n  }\n\n  session.session.scope = { vulnClasses: [...vulnClasses], exploit };\n  await atomicWrite(sessionPath, session);\n}\n\nasync function findLatestCommit(gitDir: string, commitHashes: string[]): Promise<string> {\n  if (commitHashes.length === 1) {\n    const hash = commitHashes[0];\n    if (!hash) {\n      throw new PentestError(\n        'Empty commit hash in array',\n        'filesystem',\n        false, // Non-retryable - corrupt workspace state\n        { phase: 'resume' },\n        ErrorCode.GIT_CHECKPOINT_FAILED,\n      );\n    }\n    return hash;\n  }\n\n  const result = await executeGitCommandWithRetry(\n    ['git', 'rev-list', '--max-count=1', ...commitHashes],\n    gitDir,\n    'find latest commit',\n  );\n\n  return result.stdout.trim();\n}","sourceCodeStart":1042,"sourceCodeEnd":1078,"githubUrl":"https://github.com/KeygraphHQ/shannon/blob/1ae0a142f8525410a688f0309fd003cc5b1d92de/apps/worker/src/temporal/activities.ts#L1042-L1078","documentation":"Thrown by findLatestCommit when the checkpoints array passed in has exactly one element and that element is an empty string (''). The array is built in loadResumeState by mapping completed agents to session.metrics.agents[name].checkpoint and filtering out null/undefined — but an empty string passes the != null filter, so a checkpoint recorded as '' reaches this guard. Code GIT_CHECKPOINT_FAILED, category 'filesystem', non-retryable, context.phase='resume'. It signals corrupt workspace state.","triggerScenarios":"Resuming a workspace where at least one agent is marked success in session.json and has exactly one completed agent, but its checkpoint hash field is an empty string. The filter at the call site removes null/undefined but not '', so the single empty string survives to findLatestCommit, which then refuses to pass '' to git rev-list.","commonSituations":"A prior run was interrupted while writing session.json and persisted checkpoint: '' (e.g. atomicWrite raced with a crash, or a code path recorded the agent before the checkpoint hash returned). Manual edits to session.json that blanked the checkpoint. A downgrade/upgrade wrote the field with a different (empty) default.","solutions":["Open <workspace>/.shannon/session.json (or the legacy run-root session.json) and inspect metrics.agents[*].checkpoint for an empty string.","If only one agent is affected and its deliverable is intact, populate the checkpoint from the deliverables git history (git -C <deliverables> log) or remove that agent from the success set so it re-runs.","If the workspace state is irrecoverable, start a fresh workspace rather than resuming.","Report the corruption: a non-empty checkpoint should always have been written, so capture how the prior run ended (crash/kill) to fix the writer."],"exampleFix":"// before: session.json has  metrics.agents.recon.checkpoint = \"\"\n//   resume -> findLatestCommit([''])  -> 'Empty commit hash in array'\n// after: repair the field from git history or force the agent to re-run\n//   git -C <repo>/.shannon/deliverables log --format=%H   (pick the recon commit)\n//   edit session.json: metrics.agents.recon.checkpoint = \"<hash>\"\n//   ./shannon start -u <url> -r <repo> -w <workspace>","handlingStrategy":"validation","validationCode":"// Before resuming, validate no completed agent has an empty-string checkpoint\nimport { readJson } from './json.js';\nconst session = await readJson<SessionJson>(sessionPath);\nconst empty = Object.entries(session.metrics.agents ?? {})\n  .filter(([, d]) => d.status === 'success' && (d.checkpoint ?? null) === '');\nif (empty.length) {\n  throw new Error(`Corrupt workspace: agents with empty checkpoint: ${empty.map(([n]) => n).join(', ')}`);\n}","typeGuard":"function hasNonEmptyCheckpoint(a: { status?: string; checkpoint?: string } | undefined): boolean {\n  return !!a && a.status === 'success' && typeof a.checkpoint === 'string' && a.checkpoint.length > 0;\n}","tryCatchPattern":"try {\n  await loadResumeState(input);\n} catch (e) {\n  if (e instanceof PentestError && e.code === ErrorCode.GIT_CHECKPOINT_FAILED && /Empty commit hash/.test(e.message)) {\n    // corrupt session.json — repair from deliverables git history, or start a fresh workspace\n    const hash = await repairCheckpointFromGitLog(workspace);\n    if (!hash) throw new Error('Cannot repair empty checkpoint; start a fresh workspace');\n  }\n  throw e;\n}","preventionTips":["Never persist checkpoint: '' — write the hash only after git returns it, or omit the field.","Use atomicWrite for session.json so a crash cannot leave a partially-written checkpoint.","Before resume, scan session.json for success agents with blank checkpoints and repair or drop them.","If a run was killed mid-checkpoint, start fresh rather than trusting the partial session.json."],"tags":["git","checkpoint","resume","workspace-state","data-corruption"],"backgroundTag":null,"analyzedSha":"1ae0a142f8525410a688f0309fd003cc5b1d92de","analyzedAt":"2026-08-12T17:40:03.583Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}