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
- 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
- If the tasks should NOT have changed, restore the tasks file to the pinned content (git checkout the file)
- 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
- Commit the tasks file and its manifest sha256 together — one logical change
- Compute pins with humanEvalHash, never by hand
- Make CI verify pin/file agreement before the harness runs
- On merge conflicts, resolve tasks and manifest in the same commit
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
- frozen human eval hash mismatch — set has drifted
- KV cache integrity check failed: hash mismatch
- anchor task has no query
- anchor task requires non-empty string labels
- anchorPath and anchorHash must be supplied together
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)