{"record":{"id":"8b028bc8ba66514e","repo":"abhigyanpatwari/GitNexus","slug":"shard-index-must-be-in-1-total-got-index","errorCode":null,"errorMessage":"shard index must be in 1..${total}, got ${index}","messagePattern":"shard index must be in 1\\.\\.(.+?), got (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/scripts/cross-platform-shard.ts","lineNumber":116,"sourceCode":" * 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    }\n    assigned[lightest]!.add(file);\n    loads[lightest]! += weightOf(file);\n  }","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/scripts/cross-platform-shard.ts#L98-L134","documentation":"Thrown by shardFiles() when the 1-based 'index' (which shard to select) is not an integer, is less than 1, or exceeds 'total'. The function is called independently on each CI runner, so every runner must agree on the same (index, total) pair to produce a covering, non-overlapping partition.","triggerScenarios":"Calling shardFiles(files, 0, 3) (zero-based index mistake), shardFiles(files, 4, 3) (index exceeds total), shardFiles(files, 1.5, 3), or shardFiles(files, NaN, 3).","commonSituations":"Treating the shard index as zero-based when the API expects 1-based (the most common cause); a CI matrix that numbers shards 1..N but a job variable offset by one; reading the index from an env var that was never validated.","solutions":["Ensure index is 1-based and within [1, total]: if your source is zero-based, pass index0 + 1.","Validate the pair together: if (!(index >= 1 && index <= total)) throw a clearer upstream error.","Coerce from env: Math.min(total, Math.max(1, Math.floor(Number(process.env.SHARD_INDEX) || 1)))."],"exampleFix":"// before (zero-based matrix variable i)\nconst mine = shardFiles(files, i, total);\n\n// after\nconst mine = shardFiles(files, i + 1, total);","handlingStrategy":"validation","validationCode":"function safeIndex(index, total) {\n  const i = Math.floor(Number(index));\n  if (!Number.isInteger(i) || i < 1 || i > total) {\n    throw new Error(`shard index ${index} out of range 1..${total}`);\n  }\n  return i;\n}","typeGuard":"function isValidShardIndex(index: unknown, total: number): index is number {\n  return typeof index === 'number' && Number.isInteger(index) && index >= 1 && index <= total;\n}","tryCatchPattern":null,"preventionTips":["Remember shardFiles uses 1-based indexing; convert zero-based matrix vars with +1.","Validate index against total in the same function that parses the shard flag.","Log the resolved (index, total) pair at the start of the runner for quick diagnosis."],"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"}