garrytan/gstack · error · Error
Unknown argument: ${arg}
Error message
Unknown argument: ${arg} What it means
Thrown by parseCliOptions() in scripts/test-free-shards.ts:260 when argv contains a token that is not one of the five accepted flags (--dry-run, --list, --windows-only, --shards, --shard). The parser is intentionally strict — it has no default/ignore branch, so any unrecognized argument (or any positional, since none are supported) aborts before file discovery runs. This surfaces typos and stale CI flags early instead of letting them be silently swallowed.
Source
Thrown at scripts/test-free-shards.ts:260
const arg = argv[index];
if (arg === '--dry-run') { dryRun = true; continue; }
if (arg === '--list') { listOnly = true; continue; }
if (arg === '--windows-only') { windowsOnly = true; continue; }
if (arg === '--shards') {
const value = argv[index + 1];
if (!value) throw new Error('Missing value for --shards');
shardCount = Number.parseInt(value, 10);
index += 1;
continue;
}
if (arg === '--shard') {
const value = argv[index + 1];
if (!value) throw new Error('Missing value for --shard');
shardIndex = Number.parseInt(value, 10);
index += 1;
continue;
}
throw new Error(`Unknown argument: ${arg}`);
}
return { dryRun, listOnly, windowsOnly, shardCount, shardIndex };
}
function formatShardSummary(shards: string[][]): string[] {
return shards.map((files, index) => {
const preview = files.slice(0, 3).join(', ');
const suffix = files.length > 3 ? ', ...' : '';
return `Shard ${index + 1}/${shards.length}: ${files.length} files${preview ? ` -> ${preview}${suffix}` : ''}`;
});
}
function runShard(files: string[], shardNumber: number, totalShards: number): number {
const header = `[test:free] shard ${shardNumber}/${totalShards} (${files.length} files)`;
console.log(header);
const result = spawnSync(process.execPath, buildShardArgs(files), {
cwd: ROOT,View on GitHub (pinned to 94993f7401)
Solutions
- Run with --list (no other args) to confirm the script works and see the accepted flags documented in the header comment at lines 20-25.
- Check the exact flag spelling against the five parsers in parseCliOptions (lines 243-258): --dry-run, --list, --windows-only, --shards <N>, --shard <N>. Match one exactly.
- If you used an equals sign (--shards=4), switch to space-separated form: --shards 4. The parser reads the next argv slot, not an inline value.
- If the flag came from a CI workflow file (.github/workflows/*.yml), update that file to a supported flag, or add the missing flag to parseCliOptions if the workflow is the source of truth.
- Remove any positional arguments — this script takes none; test selection is by directory roots (browse/test, test, make-pdf/test) hardcoded at line 32.
Example fix
// before (typo + equals form) bun run scripts/test-free-shards.ts --list-only --shards=4 --shard 1 // after bun run scripts/test-free-shards.ts --list --shards 4 --shard 1
Defensive patterns
Strategy: validation
Validate before calling
// Validate argv before calling the parser, or pre-check against the known set.
const ACCEPTED = new Set(['--dry-run', '--list', '--windows-only', '--shards', '--shard']);
const argv = process.argv.slice(2);
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (ACCEPTED.has(a)) { if (a === '--shards' || a === '--shard') i += 1; continue; }
console.error(`Unknown argument: ${a}. Accepted: ${[...ACCEPTED].join(', ')}`);
console.error('Usage: --dry-run | --list | --windows-only | --shards <N> | --shard <N>');
process.exit(2);
} Type guard
// Narrow a raw string to a known flag.
const FLAGS = ['--dry-run', '--list', '--windows-only', '--shards', '--shard'] as const;
type Flag = typeof FLAGS[number];
function isFlag(v: string): v is Flag { return (FLAGS as readonly string[]).includes(v); } Try / catch
// The parser throws synchronously; catch at the CLI entry point to print usage and exit 2,
// distinct from test failures (which exit non-zero from spawnSync).
if (import.meta.main) {
try { process.exitCode = main(); }
catch (err) {
console.error(`[test-free-shards] ${(err as Error).message}`);
console.error('Accepted flags: --dry-run --list --windows-only --shards <N> --shard <N>');
process.exitCode = 2;
}
} Prevention
- Keep a single ACCEPTED set as the source of truth and derive both the parser branches and any usage/help text from it, so a new flag can't exist in one place and not the other.
- Add a --help flag that prints accepted flags; the strict parser currently has no escape hatch for discovery.
- Pin the script version in CI so a flag rename in a newer revision surfaces as a review item, not a midnight abort.
- Never pass equals-form flags (--shards=4); this parser only consumes the next argv slot.
When it happens
Trigger: Running `bun run scripts/test-free-shards.ts <bad>` where <bad> is anything other than the five supported flags. Concretely: a typo like `--shard-only` or `--list-only` (the real flag is `--list`), passing `--help` or `-h` (not handled), passing a positional like a filename (`scripts/test-free-shards.ts foo.test.ts`), passing `--shards=4` with the equals sign (parser only accepts `--shards 4` space-separated), or a CI matrix that still references a renamed/removed flag from an older version of the script.
Common situations: A CI workflow copied from the McGluut/gstack fork references a flag (e.g. `--filter`, `--quiet`) that this upstream version never implemented. A developer tries `--windows` shortening instead of `--windows-only`. Someone passes `--shards=4` Bash-style. A new flag was added in the fork but not here, or vice versa, and the wrong binary is on PATH.
Related errors
- --shard must be between 1 and ${shards.length}. Received: ${
- Unknown host: ${val}. Use ${ALL_HOST_NAMES.join(', ')}, or a
- Unknown model: ${val}. Use ${ALL_MODEL_NAMES.join(', ')}, or
- Unknown catalog mode: ${val}. Use 'trim' (default) or 'full'
- Unknown explain level: ${val}. Use 'default' or 'terse'.
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/a7e2b1cb2abaf618.
Report an issue: GitHub.