ruvnet/ruflo · error

unsupported flywheel anchor schema

Error message

unsupported flywheel anchor schema: ${parsed.schemaVersion}

What it means

Anchor task files may declare `schemaVersion`; when present it must equal `ruflo.flywheel-anchor/v1` (PROJECT_ANCHOR_SCHEMA) or parsing aborts before any task is read. This keeps anchor format evolution explicit: a file written for a different schema version fails loudly instead of being misinterpreted.

Solutions

  1. Set `schemaVersion` to exactly `ruflo.flywheel-anchor/v1`, or omit the field entirely (it is optional)
  2. Regenerate the anchor file with the current CLI version rather than hand-editing
  3. Check release notes for schema migrations if your version expects a different identifier

Example fix

// before
{ "schemaVersion": "flywheel-anchor/v2", "tasks": [ ... ] }

// after
{ "schemaVersion": "ruflo.flywheel-anchor/v1", "tasks": [ ... ] }
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';

const PROJECT_ANCHOR_SCHEMA = 'ruflo.flywheel-anchor/v1';

function anchorSchemaOk(file: string): boolean {
  const parsed = JSON.parse(readFileSync(file, 'utf8'));
  return parsed.schemaVersion === undefined || parsed.schemaVersion === PROJECT_ANCHOR_SCHEMA;
}

Type guard

import type { HumanEvalTask } from '@claude-flow/cli/dist/services/harness-frozen-eval.js';

const PROJECT_ANCHOR_SCHEMA = 'ruflo.flywheel-anchor/v1';

interface AnchorFile { schemaVersion?: string; tasks?: HumanEvalTask[]; }

function isV1AnchorFile(v: unknown): v is AnchorFile {
  if (typeof v !== 'object' || v === null) return false;
  const o = v as Record<string, unknown>;
  return o.schemaVersion === undefined || o.schemaVersion === PROJECT_ANCHOR_SCHEMA;
}

Try / catch

try {
  return loadEffectiveFlywheelAnchor(root, opts);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('unsupported flywheel anchor schema')) {
    throw new Error(`Anchor file schema is ${e.message}. Expected '${PROJECT_ANCHOR_SCHEMA}' — fix or omit schemaVersion in the tasks file.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: A tasks JSON carrying a different or typo'd `schemaVersion` (e.g. 'v2', 'flywheel-anchor/v0'), the manifest schema string pasted into the tasks file, or a hand-authored file guessing at the field's value.

Common situations: Upgrading claude-flow versions that change the anchor schema; copy-pasting between the manifest and tasks files; downstream tools emitting their own version string.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-08-18). Data as JSON: /api/errors/e9e2473168a1888a. Report an issue: GitHub.

Appendix: source

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

  if (escapes(rootLexical) && escapes(rootPhysical)) {
    throw new Error('flywheel anchor path must stay inside project root');
  }
  const actual = realpathSync(absolute);
  const physical = relative(rootPhysical, actual);
  if (physical === '..' || physical.startsWith(`..${sep}`) || isAbsolute(physical)) {
    throw new Error('flywheel anchor symlink escapes project root');
  }
  return actual;
}

function parseTasks(path: string): { version: string; tasks: HumanEvalTask[] } {
  const parsed = JSON.parse(readFileSync(path, 'utf8')) as {
    schemaVersion?: string;
    version?: string;
    tasks?: HumanEvalTask[];
  };
  if (parsed.schemaVersion && parsed.schemaVersion !== PROJECT_ANCHOR_SCHEMA) {
    throw new Error(`unsupported flywheel anchor schema: ${parsed.schemaVersion}`);
  }
  if (!Array.isArray(parsed.tasks) || parsed.tasks.length < 4) {
    throw new Error('project flywheel anchor requires at least 4 labelled tasks');
  }
  const ids = new Set<string>();
  for (const [index, task] of parsed.tasks.entries()) {
    if (!task || typeof task.id !== 'string' || !/^[A-Za-z0-9._-]{1,128}$/.test(task.id)) {
      throw new Error(`invalid anchor task id at index ${index}`);
    }
    if (ids.has(task.id)) throw new Error(`duplicate anchor task id: ${task.id}`);
    ids.add(task.id);
    if (typeof task.q !== 'string' || task.q.trim().length === 0) {
      throw new Error(`anchor task ${task.id} has no query`);
    }
    if (!Array.isArray(task.labels) || task.labels.length === 0 || task.labels.some((label) => typeof label !== 'string' || !label.trim())) {
      throw new Error(`anchor task ${task.id} requires non-empty string labels`);
    }
  }

View on GitHub (pinned to 2602b642d9)