ruvnet/ruflo · error

project flywheel anchor hash mismatch

Error message

project flywheel anchor hash mismatch (got ${actualHash}, pinned ${normalizeHash(expectedHash)})

What it means

When an anchor is pinned by hash — via the `anchorHash` option or a manifest's `sha256` — `toSelection` recomputes `humanEvalHash` over the parsed tasks (canonicalized, id-sorted, order-independent) and requires an exact match with the pinned value. Any content drift between the tasks file and its pin aborts anchor selection, printing both got and pinned hashes.

Solutions

  1. If the tasks changed intentionally, update the pin to the 'got' value from the message — after verifying those task contents are really what you reviewed
  2. If the tasks should NOT have changed, restore the tasks file to the pinned content (git checkout the file)
  3. Keep the tasks file and its manifest sha256 in the same commit so they cannot drift apart

Example fix

// before: manifest pin left stale after task edits
{ "schemaVersion": "ruflo.flywheel-anchor-manifest/v1", "path": ".claude/eval/tasks.json", "sha256": "sha256:old…" }

// after: pin updated to the recomputed hash (the 'got' value in the error)
{ "schemaVersion": "ruflo.flywheel-anchor-manifest/v1", "path": ".claude/eval/tasks.json", "sha256": "sha256:6096e4…newhash" }
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';
import { humanEvalHash } from '@claude-flow/cli/dist/services/harness-frozen-eval.js';

// Verify pin/file agreement BEFORE the harness runs.
function anchorPinMatches(tasksFile: string, pinnedSha256: string): boolean {
  const tasks = JSON.parse(readFileSync(tasksFile, 'utf8')).tasks ?? [];
  const actual = humanEvalHash(tasks);
  const pinned = pinnedSha256.trim().toLowerCase();
  return actual === (pinned.startsWith('sha256:') ? pinned : `sha256:${pinned}`);
}

if (!anchorPinMatches('.claude/eval/tasks.json', manifest.sha256)) {
  throw new Error('anchor tasks drifted from pinned hash — update the manifest sha256 in the same commit');
}

Try / catch

try {
  return loadEffectiveFlywheelAnchor(root, opts);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('project flywheel anchor hash mismatch')) {
    // Decide deliberately: restore the file (pin is right) or re-pin (file is right).
    // Never paste the 'got' hash blindly — review the task diff first.
    throw new Error(`Anchor pin mismatch: ${e.message}. Either git-checkout the tasks file or update the pin after review.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Editing the tasks file without updating the manifest's `sha256`; a wrong hash pasted into the manifest or passed as `anchorHash`; two copies of the tasks file that diverged; task edits that change ids, queries, or labels (whitespace alone in JSON structure does not matter — content does).

Common situations: Iterating on anchor tasks and forgetting the pin; CI regenerating tasks but committing only one of file/hash; merge conflicts resolved for the tasks file but not the manifest.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-08-18). Data as JSON: /api/errors/5c3e3781cfb0b5ca. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/services/harness-project-anchor.ts:121

    ids.add(task.id);
    if (typeof task.q !== 'string' || task.q.trim().length === 0) {
      throw new Error(`anchor task ${task.id} has no query`);
    }
    if (!Array.isArray(task.labels) || task.labels.length === 0 || task.labels.some((label) => typeof label !== 'string' || !label.trim())) {
      throw new Error(`anchor task ${task.id} requires non-empty string labels`);
    }
  }
  return { version: parsed.version ?? 'project-anchor-v1', tasks: parsed.tasks };
}

function toSelection(
  path: string,
  expectedHash: string,
): FlywheelAnchorSelection {
  const parsed = parseTasks(path);
  const actualHash = humanEvalHash(parsed.tasks);
  if (actualHash !== normalizeHash(expectedHash)) {
    throw new Error(`project flywheel anchor hash mismatch (got ${actualHash}, pinned ${normalizeHash(expectedHash)})`);
  }
  return {
    version: parsed.version,
    anchorRef: actualHash,
    source: 'project',
    path,
    tasks: parsed.tasks.map((task) => ({
      id: task.id,
      input: { id: task.id, q: task.q },
      expected: task.labels,
    })),
  };
}

function isRufloRepository(projectRoot: string): boolean {
  try {
    const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8')) as {
      name?: string;

View on GitHub (pinned to 2602b642d9)