ruvnet/ruflo · error
project flywheel anchor requires at least 4 labelled tasks
Error message
project flywheel anchor requires at least 4 labelled tasks
What it means
A project flywheel anchor must contain at least 4 labelled tasks: `parsed.tasks` must be an array with length >= 4. The floor exists because flywheel promotion decisions are statistical — with fewer than four red/blue tasks the anchor cannot meaningfully distinguish improvement from noise, so the harness refuses to anchor on it.
Solutions
- Author at least 4 distinct labelled tasks (each with id, q, labels) representing real retrieval work in the project
- If you cannot produce 4 meaningful tasks, the project is not ready for a custom anchor — reconsider running the flywheel harness on it
- Lint the file locally (array, length >= 4) before wiring it into a manifest
Example fix
// before
{ "schemaVersion": "ruflo.flywheel-anchor/v1", "tasks": [ {t1}, {t2} ] }
// after: four or more labelled tasks
{ "schemaVersion": "ruflo.flywheel-anchor/v1", "tasks": [ {t1}, {t2}, {t3}, {t4} ] } Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from 'node:fs';
function anchorTaskCountOk(file: string): boolean {
const parsed = JSON.parse(readFileSync(file, 'utf8'));
return Array.isArray(parsed.tasks) && parsed.tasks.length >= 4;
} Try / catch
try {
return loadEffectiveFlywheelAnchor(root, opts);
} catch (e) {
if (e?.message === 'project flywheel anchor requires at least 4 labelled tasks') {
throw new Error(`Anchor '${opts.anchorPath}' has fewer than 4 tasks — add labelled tasks until the corpus is statistically usable.`);
}
throw e;
} Prevention
- Author at least 4 real retrieval tasks before wiring an anchor
- Treat a too-small corpus as a design smell, not just a validation error
- Lint the tasks file in CI: array present, length >= 4
When it happens
Trigger: An anchor tasks JSON with 0–3 entries in its `tasks` array, or `tasks` missing/not an array — often a trimmed example file or a quickly hand-made one.
Common situations: Users copying a 2-task example from docs; deleting 'uninteresting' tasks to speed up evals; converting a small test fixture into an anchor.
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
- anchor task has no query
- anchor task requires non-empty string labels
- duplicate anchor task id
- flywheel anchor manifest requires path and sha256
- invalid anchor task id at index
AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-08-18).
Data as JSON: /api/errors/dbee56bcedf62be1.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/services/harness-project-anchor.ts:95
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`);
}
}
return { version: parsed.version ?? 'project-anchor-v1', tasks: parsed.tasks };
}
View on GitHub (pinned to 2602b642d9)