ruvnet/ruflo · error · Error

tuneDistillation: empty grid — supply at least one value per

Error message

tuneDistillation: empty grid — supply at least one value per grid axis

What it means

tuneDistillation builds candidate configs as the cartesian product of grid.batchSize x grid.dedupDistance x grid.promoteThreshold (each defaulting to a non-empty array). It throws only if the resulting configs list is empty, which happens only when a caller explicitly passes an empty array for every supplied axis (axes that are omitted fall back to defaults).

Source

Thrown at v3/@claude-flow/cli/src/services/distill-tuning.ts:235

  if (!Database) {
    throw new Error('tuneDistillation: better-sqlite3 unavailable — cannot run the tuning harness');
  }

  const sourceChecksumBefore = sha256File(dbPath);

  const batchSizes = grid.batchSize ?? DEFAULT_GRID_BATCH_SIZE;
  const dedupDistances = grid.dedupDistance ?? DEFAULT_GRID_DEDUP_DISTANCE;
  const promoteThresholds = grid.promoteThreshold ?? DEFAULT_GRID_PROMOTE_THRESHOLD;
  const configs: TuningConfig[] = [];
  for (const batchSize of batchSizes) {
    for (const dedupDistance of dedupDistances) {
      for (const promoteThreshold of promoteThresholds) {
        configs.push({ batchSize, dedupDistance, promoteThreshold });
      }
    }
  }
  if (configs.length === 0) {
    throw new Error('tuneDistillation: empty grid — supply at least one value per grid axis');
  }

  // ── Splits + query sets: a dedicated read-only copy, never the source itself ──
  const readCopy = copyToTemp(dbPath, tmpDir, 'split-read');
  let outerSplit: TimeSplit;
  let innerSplit: TimeSplit;
  let trainQuerySet: QueryEntry[];
  let heldOutQuerySet: QueryEntry[];
  let heldOutBaselineIndex: PatternIndexEntry[];
  try {
    const readDb = new Database(readCopy, { readonly: true });
    try {
      outerSplit = computeTimeSplit(readDb, trainFraction);
      assertDisjoint(outerSplit.trainRowids, outerSplit.heldOutRowids);

      innerSplit = computeTimeSplit(readDb, trainFraction, {
        extraWhere: `rowid <= ${outerSplit.trainBoundaryRowid}`,
      });

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Validate the grid before calling: each supplied axis must be a non-empty array of numbers.
  2. Omit axes you do not want to vary so they fall back to DEFAULT_GRID_BATCH_SIZE / DEFAULT_GRID_DEDUP_DISTANCE / DEFAULT_GRID_PROMOTE_THRESHOLD.
  3. If computing axes dynamically, default filtered-out arrays to a singleton like [100].

Example fix

// before
tuneDistillation({ dbPath, grid: { batchSize: [], dedupDistance: [], promoteThreshold: [] } }); // throws 'empty grid'

// after
const grid = {
  batchSize: computedBatchSizes.length ? computedBatchSizes : [200],
  dedupDistance: computedDedups.length ? computedDedups : [0.1],
  promoteThreshold: computedThresholds.length ? computedThresholds : [{ min: 0.5 }],
};
await tuneDistillation({ dbPath, grid });
Defensive patterns

Strategy: validation

Validate before calling

function nonEmpty<T>(xs: T[] | undefined, fallback: T[]): T[] {
  return xs && xs.length ? xs : fallback;
}
const grid = {
  batchSize: nonEmpty(options.grid?.batchSize, [200]),
  dedupDistance: nonEmpty(options.grid?.dedupDistance, [0.1]),
  promoteThreshold: nonEmpty(options.grid?.promoteThreshold, [defaultThreshold]),
};
await tuneDistillation({ dbPath, grid });

Type guard

function isNonEmptyNumberArray(xs: unknown): xs is number[] {
  return Array.isArray(xs) && xs.length > 0 && xs.every((x) => typeof x === 'number');
}

Prevention

When it happens

Trigger: Passing grid: { batchSize: [], dedupDistance: [], promoteThreshold: [] }, or supplying one axis as a non-empty array but the other supplied axes as empty arrays such that the product is empty.

Common situations: A config-driven grid where all axes resolve to [] due to a templating bug; misreading the API and passing grid values nested under a wrong key so the destructured axes stay empty; programmatic grid generation that filters every value out.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/633770e8435c9cb7. Report an issue: GitHub.