{"record":{"id":"edbbc73dab532072","repo":"ruvnet/ruflo","slug":"anchorpath-and-anchorhash-must-be-supplied-togethe","errorCode":null,"errorMessage":"anchorPath and anchorHash must be supplied together","messagePattern":"anchorPath and anchorHash must be supplied together","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/harness-project-anchor.ts","lineNumber":156,"sourceCode":"    const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8')) as {\n      name?: string;\n      repository?: string | { url?: string };\n    };\n    const repository = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;\n    return ['claude-flow', 'ruflo', '@claude-flow/cli'].includes(pkg.name ?? '')\n      && /github\\.com[/:]ruvnet\\/(?:ruflo|claude-flow)(?:\\.git)?$/i.test(repository ?? '');\n  } catch {\n    return false;\n  }\n}\n\nexport function loadEffectiveFlywheelAnchor(\n  projectRoot: string,\n  options: LoadFlywheelAnchorOptions = {},\n): FlywheelAnchorSelection {\n  const root = resolve(projectRoot);\n  if (!!options.anchorPath !== !!options.anchorHash) {\n    throw new Error('anchorPath and anchorHash must be supplied together');\n  }\n  if (options.anchorPath && options.anchorHash) {\n    return toSelection(containedPath(root, options.anchorPath), options.anchorHash);\n  }\n\n  const manifestCandidate = options.manifestPath ?? DEFAULT_PROJECT_ANCHOR_MANIFEST;\n  const manifestPath = isAbsolute(manifestCandidate)\n    ? manifestCandidate\n    : resolve(root, manifestCandidate);\n  if (existsSync(manifestPath)) {\n    const containedManifest = containedPath(root, manifestPath);\n    const manifest = JSON.parse(readFileSync(containedManifest, 'utf8')) as ProjectAnchorManifest;\n    if (manifest.schemaVersion !== PROJECT_ANCHOR_MANIFEST_SCHEMA) {\n      throw new Error(`unsupported flywheel anchor manifest schema: ${manifest.schemaVersion}`);\n    }\n    if (typeof manifest.path !== 'string' || typeof manifest.sha256 !== 'string') {\n      throw new Error('flywheel anchor manifest requires path and sha256');\n    }","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/services/harness-project-anchor.ts#L138-L174","documentation":"`loadEffectiveFlywheelAnchor` treats `anchorPath` and `anchorHash` as a mandatory pair: supplying exactly one of them throws (`!!anchorPath !== !!anchorHash`). The rule exists because a path without a hash would load an *unverified* anchor — every explicitly selected anchor must be content-pinned, which is the whole point of #2840's fail-closed design.","triggerScenarios":"Calling `loadEffectiveFlywheelAnchor(root, { anchorPath: '.claude/eval/tasks.json' })` without `anchorHash`, or passing only a hash (e.g. a config where the path lives in another field).","commonSituations":"Config schemas modelling path and hash as independent optional fields; partial config merges dropping one key; copy-pasting half of an example invocation.","solutions":["Supply both: `anchorPath` plus the `sha256:…` hash of that file's tasks","Or supply neither and let resolution fall through to the manifest at `.claude/eval/flywheel-anchor.manifest.json` (or the default chain)","Compute the hash with the exported `humanEvalHash(tasks)` helper instead of hand-crafting it"],"exampleFix":"// before\nloadEffectiveFlywheelAnchor(root, { anchorPath: 'eval/tasks.json' });\n\n// after: path and hash always travel together\nloadEffectiveFlywheelAnchor(root, {\n  anchorPath: 'eval/tasks.json',\n  anchorHash: humanEvalHash(tasks), // 'sha256:<64 hex>'\n});","handlingStrategy":"validation","validationCode":"function anchorOptionsPaired(opts: { anchorPath?: string; anchorHash?: string }): boolean {\n  return opts.anchorPath === undefined ? opts.anchorHash === undefined : opts.anchorHash !== undefined;\n}\n\nif (!anchorOptionsPaired(opts)) {\n  throw new Error('anchorPath and anchorHash must be supplied together (or both omitted)');\n}","typeGuard":"interface AnchorOptions { anchorPath?: string; anchorHash?: string; manifestPath?: string; }\n\nfunction isPairedAnchorOptions(\n  opts: AnchorOptions,\n): opts is AnchorOptions & ({ anchorPath: string; anchorHash: string } | { anchorPath?: undefined; anchorHash?: undefined }) {\n  return opts.anchorPath === undefined ? opts.anchorHash === undefined : opts.anchorHash !== undefined;\n}","tryCatchPattern":"try {\n  return loadEffectiveFlywheelAnchor(root, opts);\n} catch (e) {\n  if (e?.message === 'anchorPath and anchorHash must be supplied together') {\n    // either add the missing half (compute it with humanEvalHash) or drop both\n    throw new Error('Pass both anchorPath and anchorHash, or neither (manifest resolution).');\n  }\n  throw e;\n}","preventionTips":["Model anchorPath/anchorHash as a single optional object in your config, not two independent fields","Reject half-filled config at load time with a clear message","Copy invocation examples verbatim — the pair is load-bearing, not incidental"],"tags":["validation","api-contract","anchor","arguments"],"backgroundTag":"missing-required-argument","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"}