abhigyanpatwari/GitNexus · error · Error

shard total must be a positive integer, got ${total}

Error message

shard total must be a positive integer, got ${total}

What it means

Thrown by shardFiles() in the cross-platform test sharding script when the 'total' (shard count) argument is not a positive integer. This function partitions the test file list across CI runners using longest-processing-time-first scheduling, so a bad total would break the deterministic split that every runner computes independently. The guard runs before any allocation, so no partial state is produced.

Source

Thrown at gitnexus/scripts/cross-platform-shard.ts:113

 * Longest-processing-time first: sort by weight descending, then repeatedly give
 * the next file to the lightest shard so far. LPT is the standard greedy for
 * multiprocessor scheduling and is guaranteed within 4/3 of optimal — far more
 * than enough here, where the goal is only "no shard gets two monsters".
 *
 * Ties break on the file path so the partition is DETERMINISTIC: every shard
 * computes the same split independently, on a different machine, with no
 * coordination — which is what lets each runner select its own slice.
 *
 * Returns files in the input list's original order, not weight order, so failure
 * output and reruns stay readable.
 */
export function shardFiles(
  files: readonly string[],
  index: number,
  total: number,
): readonly string[] {
  if (!Number.isInteger(total) || total < 1) {
    throw new Error(`shard total must be a positive integer, got ${total}`);
  }
  if (!Number.isInteger(index) || index < 1 || index > total) {
    throw new Error(`shard index must be in 1..${total}, got ${index}`);
  }
  if (total === 1) return [...files];

  const byWeightDesc = [...files].sort((a, b) => {
    const diff = weightOf(b) - weightOf(a);
    return diff !== 0 ? diff : a.localeCompare(b);
  });

  const loads = Array.from({ length: total }, () => 0);
  const assigned = Array.from({ length: total }, () => new Set<string>());
  for (const file of byWeightDesc) {
    let lightest = 0;
    for (let i = 1; i < total; i++) {
      if (loads[i]! < loads[lightest]!) lightest = i;
    }

View on GitHub (pinned to d540b00184)

Solutions

  1. Coerce total to an integer before calling: Math.max(1, Math.floor(Number(process.env.CI_NODE_TOTAL || 1))).
  2. If total comes from the --shard=i/n flag, validate it at parseShardArg time and surface the bad value there.
  3. Default to total=1 (no sharding) when the value is missing or unparseable, if unsharded execution is an acceptable fallback for your runner.

Example fix

// before
const total = Number(process.env.TOTAL_SHARDS);
const mine = shardFiles(files, index, total);

// after
const total = Math.max(1, Math.floor(Number(process.env.TOTAL_SHARDS) || 1));
const mine = shardFiles(files, index, total);
Defensive patterns

Strategy: validation

Validate before calling

function safeShardFiles(files, index, total) {
  const t = Math.floor(Number(total));
  if (!Number.isInteger(t) || t < 1) {
    throw new Error(`invalid shard total: ${total}`);
  }
  const i = Math.floor(Number(index));
  if (!Number.isInteger(i) || i < 1 || i > t) {
    throw new Error(`invalid shard index ${index} for total ${t}`);
  }
  return shardFiles(files, i, t);
}

Type guard

function isValidShardTotal(total: unknown): total is number {
  return typeof total === 'number' && Number.isInteger(total) && total >= 1;
}

Prevention

When it happens

Trigger: Calling shardFiles(files, index, total) with total=0, total=-1, total=1.5, total=NaN, or total=Infinity. Also triggered if total is derived from an unparsed CLI/env string that was never coerced to an integer.

Common situations: An off-by-one in CI matrix generation that yields total=0 for a degenerate matrix; passing a float parsed from a division without Math.floor; reading CI_NODE_TOTAL from an env var that is undefined (yielding NaN) or set to an empty string.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/429ce23f7c11187c. Report an issue: GitHub.