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
- 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.
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
- 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.
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
- shard index must be in 1..${total}, got ${index}
- Malformed --shard arg '${malformed}' — expected --shard=<ind
- [understand-quickly] expected id of the form "owner/repo", g
- Unsupported provider: ${(config as any).provider}
- Invalid backend URL: must be a well-formed http:// or https:
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/429ce23f7c11187c.
Report an issue: GitHub.