{"record":{"id":"180e0453e22aeada","repo":"ruvnet/ruflo","slug":"candidate-label-must-ingest-at-least-one-vector","errorCode":null,"errorMessage":"candidate ${label} must ingest at least one vector","messagePattern":"candidate (.+?) must ingest at least one vector","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agenticow-speculate-tools.ts","lineNumber":161,"sourceCode":"\n      const rawCandidates = input.candidates as CandidateInput[];\n      if (!Array.isArray(rawCandidates) || rawCandidates.length === 0) {\n        throw new Error('at least one candidate is required');\n      }\n      if (scoreBy === 'nearest' && !probe) {\n        throw new Error(\"scoreBy='nearest' requires a probe vector\");\n      }\n\n      // Build the generic {label, fn} candidates. Each fn ingests into its own\n      // branch handle, then (for 'nearest') probes it so we can score.\n      // Map validated label → explicit branchPath so the branchPath() resolver\n      // below is O(1) instead of re-scanning + re-validating rawCandidates per\n      // candidate (explore() calls branchPath once per candidate → was O(n²)).\n      const explicitBranchPaths = new Map<string, string>();\n      const candidates: SpeculativeCandidate<CandidateOutcome>[] = rawCandidates.map((c) => {\n        const label = validateLabel(String(c.label));\n        if (!Array.isArray(c.ingest) || c.ingest.length === 0) {\n          throw new Error(`candidate ${label} must ingest at least one vector`);\n        }\n        if (typeof c.branchPath === 'string' && c.branchPath) {\n          explicitBranchPaths.set(label, c.branchPath);\n        }\n        const records = c.ingest.map((r) => ({\n          ...(Number.isInteger(r.id) ? { id: r.id as number } : {}),\n          vector: r.vector,\n          ...(typeof r.text === 'string' ? { text: r.text } : {}),\n        }));\n        return {\n          label,\n          fn: (branch: any): CandidateOutcome => {\n            const res = branch.ingest(records);\n            const accepted = Number(res?.accepted ?? records.length);\n            let hits: Array<{ id: number; distance: number }> = [];\n            if (probe) {\n              hits = (branch.query(probe, k) || []).map((h: any) => ({\n                id: h.id,","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/mcp-tools/agenticow-speculate-tools.ts#L143-L179","documentation":"Thrown while building the candidate list for agenticow_speculate. Every candidate must carry a non-empty `ingest` array because each branch is populated solely by ingesting those vectors — an empty branch has nothing to score or promote. The message includes the validated label so the offending candidate is identifiable.","triggerScenarios":"A candidate object whose `ingest` is omitted, set to null, not an array, or an empty array []. The check runs inside rawCandidates.map after the label is validated, so the error names the candidate label.","commonSituations":"Building candidates programmatically and filtering out all ingest records before passing; passing a candidate that only declares a branchPath but no ingest; a template that stubs ingest: [] as a placeholder.","solutions":["Ensure every candidate includes ingest: [{ vector: [...] }] with at least one vector record.","If a candidate legitimately has no data, drop it from the candidates array rather than passing an empty ingest.","Validate the candidates array shape before calling the tool (see defense validationCode).","Check that serialisation did not collapse ingest into an object or string."],"exampleFix":"// before\n{ label: 'branch-a', ingest: [] }\n// after\n{ label: 'branch-a', ingest: [{ vector: embedding }] }","handlingStrategy":"validation","validationCode":"function validateCandidates(candidates) {\n  if (!Array.isArray(candidates) || candidates.length === 0) {\n    throw new Error('at least one candidate required');\n  }\n  for (const c of candidates) {\n    if (!Array.isArray(c.ingest) || c.ingest.length === 0) {\n      throw new Error(`candidate ${c.label} must ingest at least one vector`);\n    }\n  }\n}","typeGuard":"function isCandidate(c: unknown): c is { label: string; ingest: unknown[] } {\n  return typeof c === 'object' && c !== null\n    && typeof (c as any).label === 'string'\n    && Array.isArray((c as any).ingest) && (c as any).ingest.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Build candidates with a factory that always attaches a non-empty ingest array.","Filter candidates AFTER confirming ingest is non-empty, not before.","Log candidate labels during construction to localise failures."],"tags":["agenticow","mcp-tools","validation","speculative","input-validation"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}