{"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":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/harness-project-anchor.ts","lineNumber":145,"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":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/harness-project-anchor.ts#L127-L163","documentation":"Thrown by loadEffectiveFlywheelAnchor() when exactly one of options.anchorPath / options.anchorHash is set (the !! XOR is true). The two are a required pair: anchorPath points to the tasks JSON and anchorHash is the pinned sha256 that toSelection() verifies against. Supplying only one is treated as a programming error because an unverified path or an unused hash both defeat the integrity guarantee.","triggerScenarios":"Calling loadEffectiveFlywheelAnchor(root, { anchorPath: './my.json' }) without anchorHash, or { anchorHash: 'sha256:...' } without anchorPath. Most commonly a caller building the options object conditionally forgot one branch.","commonSituations":"A CLI flag parser that accepts --anchor-path and --anchor-hash as independent flags but a user passed only one; refactoring a caller to compute the hash lazily and forgetting to thread it through; an environment-variable-driven config where one var was set and the other unset.","solutions":["Pass both options together: { anchorPath, anchorHash }, or omit both to fall through to the manifest / built-in path.","If using a CLI, require the two flags to appear together (enforce at the argument parser, e.g. yargs check()).","When the hash is unknown, leave both unset and let the manifest mechanism resolve the anchor instead."],"exampleFix":"// before\nloadEffectiveFlywheelAnchor(root, { anchorPath: './eval/tasks.json' })\n// after — supply the matching pinned hash\nloadEffectiveFlywheelAnchor(root, {\n  anchorPath: './eval/tasks.json',\n  anchorHash: 'sha256:abc123...',\n})","handlingStrategy":"type-guard","validationCode":"function buildAnchorOptions(opts: { path?: string; hash?: string }) {\n  const hasPath = typeof opts.path === 'string' && opts.path.length > 0;\n  const hasHash = typeof opts.hash === 'string' && opts.hash.length > 0;\n  if (hasPath !== hasHash) {\n    throw new Error('anchorPath and anchorHash must both be set or both omitted');\n  }\n  return hasPath ? { anchorPath: opts.path, anchorHash: opts.hash } : {};\n}","typeGuard":"function isValidAnchorOptionPair(opts: { anchorPath?: unknown; anchorHash?: unknown }): boolean {\n  const p = typeof opts.anchorPath === 'string' && opts.anchorPath.length > 0;\n  const h = typeof opts.anchorHash === 'string' && opts.anchorHash.length > 0;\n  return p === h;\n}","tryCatchPattern":"try {\n  loadEffectiveFlywheelAnchor(root, opts);\n} catch (e) {\n  if (e instanceof Error && /anchorPath and anchorHash must be supplied together/.test(e.message)) {\n    // pass both or neither; fall back to the manifest path by omitting both\n  }\n  throw e;\n}","preventionTips":["Treat anchorPath/anchorHash as a single coupled option in your CLI/config layer.","In a yargs CLI, use .check() to require both flags together.","When unsure of the hash, omit both and rely on the manifest."],"tags":["flywheel-anchor","api-misuse","validation","configuration"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}