ruvnet/ruflo · error · Error

anchorPath and anchorHash must be supplied together

Error message

anchorPath and anchorHash must be supplied together

What it means

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.

Source

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

    const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8')) as {
      name?: string;
      repository?: string | { url?: string };
    };
    const repository = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;
    return ['claude-flow', 'ruflo', '@claude-flow/cli'].includes(pkg.name ?? '')
      && /github\.com[/:]ruvnet\/(?:ruflo|claude-flow)(?:\.git)?$/i.test(repository ?? '');
  } catch {
    return false;
  }
}

export function loadEffectiveFlywheelAnchor(
  projectRoot: string,
  options: LoadFlywheelAnchorOptions = {},
): FlywheelAnchorSelection {
  const root = resolve(projectRoot);
  if (!!options.anchorPath !== !!options.anchorHash) {
    throw new Error('anchorPath and anchorHash must be supplied together');
  }
  if (options.anchorPath && options.anchorHash) {
    return toSelection(containedPath(root, options.anchorPath), options.anchorHash);
  }

  const manifestCandidate = options.manifestPath ?? DEFAULT_PROJECT_ANCHOR_MANIFEST;
  const manifestPath = isAbsolute(manifestCandidate)
    ? manifestCandidate
    : resolve(root, manifestCandidate);
  if (existsSync(manifestPath)) {
    const containedManifest = containedPath(root, manifestPath);
    const manifest = JSON.parse(readFileSync(containedManifest, 'utf8')) as ProjectAnchorManifest;
    if (manifest.schemaVersion !== PROJECT_ANCHOR_MANIFEST_SCHEMA) {
      throw new Error(`unsupported flywheel anchor manifest schema: ${manifest.schemaVersion}`);
    }
    if (typeof manifest.path !== 'string' || typeof manifest.sha256 !== 'string') {
      throw new Error('flywheel anchor manifest requires path and sha256');
    }

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Pass both options together: { anchorPath, anchorHash }, or omit both to fall through to the manifest / built-in path.
  2. If using a CLI, require the two flags to appear together (enforce at the argument parser, e.g. yargs check()).
  3. When the hash is unknown, leave both unset and let the manifest mechanism resolve the anchor instead.

Example fix

// before
loadEffectiveFlywheelAnchor(root, { anchorPath: './eval/tasks.json' })
// after — supply the matching pinned hash
loadEffectiveFlywheelAnchor(root, {
  anchorPath: './eval/tasks.json',
  anchorHash: 'sha256:abc123...',
})
Defensive patterns

Strategy: type-guard

Validate before calling

function buildAnchorOptions(opts: { path?: string; hash?: string }) {
  const hasPath = typeof opts.path === 'string' && opts.path.length > 0;
  const hasHash = typeof opts.hash === 'string' && opts.hash.length > 0;
  if (hasPath !== hasHash) {
    throw new Error('anchorPath and anchorHash must both be set or both omitted');
  }
  return hasPath ? { anchorPath: opts.path, anchorHash: opts.hash } : {};
}

Type guard

function isValidAnchorOptionPair(opts: { anchorPath?: unknown; anchorHash?: unknown }): boolean {
  const p = typeof opts.anchorPath === 'string' && opts.anchorPath.length > 0;
  const h = typeof opts.anchorHash === 'string' && opts.anchorHash.length > 0;
  return p === h;
}

Try / catch

try {
  loadEffectiveFlywheelAnchor(root, opts);
} catch (e) {
  if (e instanceof Error && /anchorPath and anchorHash must be supplied together/.test(e.message)) {
    // pass both or neither; fall back to the manifest path by omitting both
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/edbbc73dab532072. Report an issue: GitHub.