{"record":{"id":"429ce23f7c11187c","repo":"abhigyanpatwari/GitNexus","slug":"shard-total-must-be-a-positive-integer-got-tota","errorCode":null,"errorMessage":"shard total must be a positive integer, got ${total}","messagePattern":"shard total must be a positive integer, got (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/scripts/cross-platform-shard.ts","lineNumber":113,"sourceCode":" * Longest-processing-time first: sort by weight descending, then repeatedly give\n * the next file to the lightest shard so far. LPT is the standard greedy for\n * multiprocessor scheduling and is guaranteed within 4/3 of optimal — far more\n * than enough here, where the goal is only \"no shard gets two monsters\".\n *\n * Ties break on the file path so the partition is DETERMINISTIC: every shard\n * computes the same split independently, on a different machine, with no\n * coordination — which is what lets each runner select its own slice.\n *\n * Returns files in the input list's original order, not weight order, so failure\n * output and reruns stay readable.\n */\nexport function shardFiles(\n  files: readonly string[],\n  index: number,\n  total: number,\n): readonly string[] {\n  if (!Number.isInteger(total) || total < 1) {\n    throw new Error(`shard total must be a positive integer, got ${total}`);\n  }\n  if (!Number.isInteger(index) || index < 1 || index > total) {\n    throw new Error(`shard index must be in 1..${total}, got ${index}`);\n  }\n  if (total === 1) return [...files];\n\n  const byWeightDesc = [...files].sort((a, b) => {\n    const diff = weightOf(b) - weightOf(a);\n    return diff !== 0 ? diff : a.localeCompare(b);\n  });\n\n  const loads = Array.from({ length: total }, () => 0);\n  const assigned = Array.from({ length: total }, () => new Set<string>());\n  for (const file of byWeightDesc) {\n    let lightest = 0;\n    for (let i = 1; i < total; i++) {\n      if (loads[i]! < loads[lightest]!) lightest = i;\n    }","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/scripts/cross-platform-shard.ts#L95-L131","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Coerce total to an integer before calling: Math.max(1, Math.floor(Number(process.env.CI_NODE_TOTAL || 1))).","If total comes from the --shard=i/n flag, validate it at parseShardArg time and surface the bad value there.","Default to total=1 (no sharding) when the value is missing or unparseable, if unsharded execution is an acceptable fallback for your runner."],"exampleFix":"// before\nconst total = Number(process.env.TOTAL_SHARDS);\nconst mine = shardFiles(files, index, total);\n\n// after\nconst total = Math.max(1, Math.floor(Number(process.env.TOTAL_SHARDS) || 1));\nconst mine = shardFiles(files, index, total);","handlingStrategy":"validation","validationCode":"function safeShardFiles(files, index, total) {\n  const t = Math.floor(Number(total));\n  if (!Number.isInteger(t) || t < 1) {\n    throw new Error(`invalid shard total: ${total}`);\n  }\n  const i = Math.floor(Number(index));\n  if (!Number.isInteger(i) || i < 1 || i > t) {\n    throw new Error(`invalid shard index ${index} for total ${t}`);\n  }\n  return shardFiles(files, i, t);\n}","typeGuard":"function isValidShardTotal(total: unknown): total is number {\n  return typeof total === 'number' && Number.isInteger(total) && total >= 1;\n}","tryCatchPattern":null,"preventionTips":["Always coerce env-derived shard counts with Math.floor and default to 1.","Keep the (index, total) pair construction in one place so they cannot disagree.","Add a unit test that shardFiles throws on total=0, total=-1, and total=1.5."],"tags":["validation","sharding","ci","testing"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}