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
- 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.
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
- 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.
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
- project flywheel anchor requires at least 4 labelled tasks
- anchor task ${task.id} has no query
- flywheel anchor manifest requires path and sha256
- unsupported flywheel anchor schema: ${parsed.schemaVersion}
- invalid anchor task id at index ${index}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/edbbc73dab532072.
Report an issue: GitHub.