KeygraphHQ/shannon · error · PentestError

GIT_CHECKPOINT_FAILED

GIT_CHECKPOINT_FAILED

Error message

Empty commit hash in array

What it means

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.

Source

Thrown at apps/worker/src/temporal/activities.ts:1060

        `Resume scope mismatch for workspace ${input.sessionId}.\n` +
          `  Original: vuln_classes=[${recorded.vulnClasses.join(', ')}], exploit=${recorded.exploit}\n` +
          `  Provided: vuln_classes=[${vulnClasses.join(', ')}], exploit=${exploit}\n` +
          `Resume requires the same scope as the original run. Start a new workspace if you want different scope.`,
        'ScopeMismatchError',
      );
    }
    return;
  }

  session.session.scope = { vulnClasses: [...vulnClasses], exploit };
  await atomicWrite(sessionPath, session);
}

async function findLatestCommit(gitDir: string, commitHashes: string[]): Promise<string> {
  if (commitHashes.length === 1) {
    const hash = commitHashes[0];
    if (!hash) {
      throw new PentestError(
        'Empty commit hash in array',
        'filesystem',
        false, // Non-retryable - corrupt workspace state
        { phase: 'resume' },
        ErrorCode.GIT_CHECKPOINT_FAILED,
      );
    }
    return hash;
  }

  const result = await executeGitCommandWithRetry(
    ['git', 'rev-list', '--max-count=1', ...commitHashes],
    gitDir,
    'find latest commit',
  );

  return result.stdout.trim();
}

View on GitHub (pinned to 1ae0a142f8)

Solutions

  1. Open <workspace>/.shannon/session.json (or the legacy run-root session.json) and inspect metrics.agents[*].checkpoint for an empty string.
  2. 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.
  3. If the workspace state is irrecoverable, start a fresh workspace rather than resuming.
  4. 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.

Example fix

// before: session.json has  metrics.agents.recon.checkpoint = ""
//   resume -> findLatestCommit([''])  -> 'Empty commit hash in array'
// after: repair the field from git history or force the agent to re-run
//   git -C <repo>/.shannon/deliverables log --format=%H   (pick the recon commit)
//   edit session.json: metrics.agents.recon.checkpoint = "<hash>"
//   ./shannon start -u <url> -r <repo> -w <workspace>
Defensive patterns

Strategy: validation

Validate before calling

// Before resuming, validate no completed agent has an empty-string checkpoint
import { readJson } from './json.js';
const session = await readJson<SessionJson>(sessionPath);
const empty = Object.entries(session.metrics.agents ?? {})
  .filter(([, d]) => d.status === 'success' && (d.checkpoint ?? null) === '');
if (empty.length) {
  throw new Error(`Corrupt workspace: agents with empty checkpoint: ${empty.map(([n]) => n).join(', ')}`);
}

Type guard

function hasNonEmptyCheckpoint(a: { status?: string; checkpoint?: string } | undefined): boolean {
  return !!a && a.status === 'success' && typeof a.checkpoint === 'string' && a.checkpoint.length > 0;
}

Try / catch

try {
  await loadResumeState(input);
} catch (e) {
  if (e instanceof PentestError && e.code === ErrorCode.GIT_CHECKPOINT_FAILED && /Empty commit hash/.test(e.message)) {
    // corrupt session.json — repair from deliverables git history, or start a fresh workspace
    const hash = await repairCheckpointFromGitLog(workspace);
    if (!hash) throw new Error('Cannot repair empty checkpoint; start a fresh workspace');
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of KeygraphHQ/shannon@1ae0a142f8 (2026-08-12). Data as JSON: /api/errors/bb48f57ecdeb4a66. Report an issue: GitHub.