{"record":{"id":"5c3e3781cfb0b5ca","repo":"ruvnet/ruflo","slug":"project-flywheel-anchor-hash-mismatch-got-actua","errorCode":null,"errorMessage":"project flywheel anchor hash mismatch (got ${actualHash}, pinned ${normalizeHash(expectedHash)})","messagePattern":"project flywheel anchor hash mismatch \\(got (.+?), pinned (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/harness-project-anchor.ts","lineNumber":121,"sourceCode":"    ids.add(task.id);\n    if (typeof task.q !== 'string' || task.q.trim().length === 0) {\n      throw new Error(`anchor task ${task.id} has no query`);\n    }\n    if (!Array.isArray(task.labels) || task.labels.length === 0 || task.labels.some((label) => typeof label !== 'string' || !label.trim())) {\n      throw new Error(`anchor task ${task.id} requires non-empty string labels`);\n    }\n  }\n  return { version: parsed.version ?? 'project-anchor-v1', tasks: parsed.tasks };\n}\n\nfunction toSelection(\n  path: string,\n  expectedHash: string,\n): FlywheelAnchorSelection {\n  const parsed = parseTasks(path);\n  const actualHash = humanEvalHash(parsed.tasks);\n  if (actualHash !== normalizeHash(expectedHash)) {\n    throw new Error(`project flywheel anchor hash mismatch (got ${actualHash}, pinned ${normalizeHash(expectedHash)})`);\n  }\n  return {\n    version: parsed.version,\n    anchorRef: actualHash,\n    source: 'project',\n    path,\n    tasks: parsed.tasks.map((task) => ({\n      id: task.id,\n      input: { id: task.id, q: task.q },\n      expected: task.labels,\n    })),\n  };\n}\n\nfunction isRufloRepository(projectRoot: string): boolean {\n  try {\n    const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8')) as {\n      name?: string;","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/services/harness-project-anchor.ts#L103-L139","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before: manifest pin left stale after task edits\n{ \"schemaVersion\": \"ruflo.flywheel-anchor-manifest/v1\", \"path\": \".claude/eval/tasks.json\", \"sha256\": \"sha256:old…\" }\n\n// after: pin updated to the recomputed hash (the 'got' value in the error)\n{ \"schemaVersion\": \"ruflo.flywheel-anchor-manifest/v1\", \"path\": \".claude/eval/tasks.json\", \"sha256\": \"sha256:6096e4…newhash\" }","handlingStrategy":"validation","validationCode":"import { readFileSync } from 'node:fs';\nimport { humanEvalHash } from '@claude-flow/cli/dist/services/harness-frozen-eval.js';\n\n// Verify pin/file agreement BEFORE the harness runs.\nfunction anchorPinMatches(tasksFile: string, pinnedSha256: string): boolean {\n  const tasks = JSON.parse(readFileSync(tasksFile, 'utf8')).tasks ?? [];\n  const actual = humanEvalHash(tasks);\n  const pinned = pinnedSha256.trim().toLowerCase();\n  return actual === (pinned.startsWith('sha256:') ? pinned : `sha256:${pinned}`);\n}\n\nif (!anchorPinMatches('.claude/eval/tasks.json', manifest.sha256)) {\n  throw new Error('anchor tasks drifted from pinned hash — update the manifest sha256 in the same commit');\n}","typeGuard":null,"tryCatchPattern":"try {\n  return loadEffectiveFlywheelAnchor(root, opts);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('project flywheel anchor hash mismatch')) {\n    // Decide deliberately: restore the file (pin is right) or re-pin (file is right).\n    // Never paste the 'got' hash blindly — review the task diff first.\n    throw new Error(`Anchor pin mismatch: ${e.message}. Either git-checkout the tasks file or update the pin after review.`);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["integrity","checksum","anchor","pinning"],"backgroundTag":"checksum-mismatch","analyzedSha":"2602b642d92234c710ffbe96bfb33007d481ceab","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}