ruvnet/ruflo · error
duplicate anchor task id
Error message
duplicate anchor task id: ${task.id} What it means
Anchor task ids must be unique within the file — validation builds a Set as it walks the tasks and the second occurrence of an id throws, naming the duplicate. Duplicate ids would corrupt receipt correlation and the order-independent corpus hash (`humanEvalHash` sorts by id).
Solutions
- Find the duplicate named in the message and give one of the two tasks a distinct id
- Dedupe programmatically when generating anchors from other sources
- Re-run after fixing — validation reports one duplicate at a time, so large files may need several passes
Example fix
// before
[ { "id": "login", "q": "..." }, { "id": "login", "q": "other..." } ]
// after
[ { "id": "login", "q": "..." }, { "id": "login-oauth", "q": "other..." } ] Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from 'node:fs';
function anchorTaskIdsUnique(file: string): { ok: boolean; duplicate?: string } {
const { tasks } = JSON.parse(readFileSync(file, 'utf8'));
const seen = new Set<string>();
for (const t of tasks ?? []) {
if (seen.has(t.id)) return { ok: false, duplicate: t.id };
seen.add(t.id);
}
return { ok: true };
} Try / catch
try {
return loadEffectiveFlywheelAnchor(root, opts);
} catch (e) {
if (e instanceof Error && e.message.startsWith('duplicate anchor task id')) {
const dup = e.message.split(': ')[1];
throw new Error(`Two tasks share id '${dup}' — rename one; ids are lineage correlation keys and must be unique.`);
}
throw e;
} Prevention
- Dedupe in generators: key a Map by id and fail on collision
- After merging anchor files, run a uniqueness check before committing
- Rename the id whenever you clone a task to variant it
When it happens
Trigger: Two tasks sharing an id, typically after merging two anchor files or copy-pasting a task and editing its query but forgetting to change its id.
Common situations: Manual curation merges; generators appending tasks without dedupe; refactors that rewrite queries but leave ids untouched.
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
- flywheel anchor manifest requires path and sha256
- invalid anchor task id at index
- project flywheel anchor requires at least 4 labelled tasks
AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-08-18).
Data as JSON: /api/errors/96a65d128473c3e0.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/services/harness-project-anchor.ts:102
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 };
}
function toSelection(
path: string,
expectedHash: string,
): FlywheelAnchorSelection {
const parsed = parseTasks(path);
const actualHash = humanEvalHash(parsed.tasks);
if (actualHash !== normalizeHash(expectedHash)) {View on GitHub (pinned to 2602b642d9)