{"record":{"id":"70e62f9ff600df4c","repo":"ruvnet/ruflo","slug":"anchor-task-task-id-requires-non-empty-string-l","errorCode":null,"errorMessage":"anchor task ${task.id} requires non-empty string labels","messagePattern":"anchor task (.+?) requires non-empty string labels","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/harness-project-anchor.ts","lineNumber":97,"sourceCode":"  };\n  if (parsed.schemaVersion && parsed.schemaVersion !== PROJECT_ANCHOR_SCHEMA) {\n    throw new Error(`unsupported flywheel anchor schema: ${parsed.schemaVersion}`);\n  }\n  if (!Array.isArray(parsed.tasks) || parsed.tasks.length < 4) {\n    throw new Error('project flywheel anchor requires at least 4 labelled tasks');\n  }\n  const ids = new Set<string>();\n  for (const [index, task] of parsed.tasks.entries()) {\n    if (!task || typeof task.id !== 'string' || !/^[A-Za-z0-9._-]{1,128}$/.test(task.id)) {\n      throw new Error(`invalid anchor task id at index ${index}`);\n    }\n    if (ids.has(task.id)) throw new Error(`duplicate anchor task id: ${task.id}`);\n    ids.add(task.id);\n    if (typeof task.q !== 'string' || task.q.trim().length === 0) {\n      throw new Error(`anchor task ${task.id} has no query`);\n    }\n    if (!Array.isArray(task.labels) || task.labels.length === 0 || task.labels.some((label) => typeof label !== 'string' || !label.trim())) {\n      throw new Error(`anchor task ${task.id} requires non-empty string labels`);\n    }\n  }\n  return { version: parsed.version ?? 'project-anchor-v1', tasks: parsed.tasks };\n}\n\nfunction toSelection(\n  path: string,\n  expectedHash: string,\n): FlywheelAnchorSelection {\n  const parsed = parseTasks(path);\n  const actualHash = humanEvalHash(parsed.tasks);\n  if (actualHash !== normalizeHash(expectedHash)) {\n    throw new Error(`project flywheel anchor hash mismatch (got ${actualHash}, pinned ${normalizeHash(expectedHash)})`);\n  }\n  return {\n    version: parsed.version,\n    anchorRef: actualHash,\n    source: 'project',","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/harness-project-anchor.ts#L79-L115","documentation":"Thrown per-task when task.labels is missing, not an array, an empty array, or contains any element that is not a non-empty string (after trim). Labels are the expected-relevance markers the flywheel uses to score retrieval, so each task must carry at least one meaningful label. The check is strict: a labels value of [\"\", \"  \"] fails because every element must be a non-blank string.","triggerScenarios":"A task object where labels is undefined, a string instead of an array, [], or an array containing non-string values (numbers, nulls, objects) or blank strings. The some() predicate fails on the first offending element.","commonSituations":"Using a single label string instead of a one-element array (labels: \"auth\" vs labels: [\"auth\"]); an auto-labeller that emitted nulls for unmatched tasks; copying labels from a CSV that introduced empty trailing elements; forgetting the field on tasks added late.","solutions":["Ensure labels is an array of at least one non-empty string, e.g. [\"auth\", \"middleware\"].","Filter out null/blank entries when generating labels: labels.filter(l => typeof l === 'string' && l.trim()).","Verify Array.isArray(task.labels) && task.labels.length > 0 for every task before saving."],"exampleFix":"// before\n{\"id\":\"t1\",\"q\":\"...\",\"labels\":\"auth\"}\n// after — wrap in an array, all non-empty strings\n{\"id\":\"t1\",\"q\":\"...\",\"labels\":[\"auth\"]}","handlingStrategy":"validation","validationCode":"function assertLabels(tasks: { id: string; labels?: unknown }[]): void {\n  for (const t of tasks) {\n    const labels = t.labels;\n    if (!Array.isArray(labels) || labels.length === 0\n        || labels.some(l => typeof l !== 'string' || !l.trim())) {\n      throw new Error(`task ${t.id} has invalid labels`);\n    }\n  }\n}","typeGuard":"function hasValidLabels(task: unknown): boolean {\n  if (typeof task !== 'object' || task === null) return false;\n  const labels = (task as { labels?: unknown }).labels;\n  return Array.isArray(labels) && labels.length > 0\n    && labels.every(l => typeof l === 'string' && l.trim().length > 0);\n}","tryCatchPattern":"try {\n  loadEffectiveFlywheelAnchor(root, opts);\n} catch (e) {\n  if (e instanceof Error && /requires non-empty string labels/.test(e.message)) {\n    const id = e.message.match(/task (.+?) requires/)?.[1];\n    // fix that task's labels array, then retry\n  }\n  throw e;\n}","preventionTips":["Always wrap labels in an array, even for a single label.","Filter out null/blank labels during generation.","Assert Array.isArray(labels) && labels.length in your anchor schema tests."],"tags":["flywheel-anchor","validation","labels","json"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}