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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

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


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/a7e2b1cb2abaf618. Report an issue: GitHub.