garrytan/gstack · error · Error

No free test files were discovered.

Error message

No free test files were discovered.

What it means

Thrown by main() in scripts/test-free-shards.ts:292 when collectFreeTestFiles() returns an empty array. collectFreeTestFiles() (lines 156-169) walks the three hardcoded TEST_ROOTS (browse/test, test, make-pdf/test) relative to ROOT (the repo root, resolved at line 31 from import.meta.dir/..), keeps files matching TEST_FILE_REGEX, and excludes any file matching the PAID_EVAL_TESTS regexes (lines 37-44). An empty result means none of those directories existed, none contained a *.test.* file, or every discovered file was classified as a paid-eval test.

Source

Thrown at scripts/test-free-shards.ts:292

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,
    stdio: 'inherit',
    env: process.env,
  });
  if (result.status !== 0) {
    console.error(`${header} failed with exit code ${result.status ?? 1}`);
  }
  return result.status ?? 1;
}

function main(): number {
  const options = parseCliOptions(process.argv.slice(2));
  const allFiles = collectFreeTestFiles();
  if (allFiles.length === 0) {
    throw new Error('No free test files were discovered.');
  }

  let files = allFiles;
  let curationReport: CurationResult | null = null;
  if (options.windowsOnly) {
    curationReport = curateWindowsSafe(allFiles);
    files = curationReport.safe;
    console.log(`[test:free] curated ${files.length} Windows-safe tests (${curationReport.excluded.length} excluded)`);
    if (options.listOnly && curationReport.excluded.length > 0) {
      console.log('\nExcluded (POSIX-fragile):');
      for (const { file, reason } of curationReport.excluded) {
        console.log(`  - ${file}  [${reason}]`);
      }
    }
  }

  if (options.listOnly) {
    console.log(`\nDiscovered ${files.length} test files.`);

View on GitHub (pinned to 94993f7401)

Solutions

  1. Confirm cwd is the repo root: the script resolves ROOT from import.meta.dir/.., so run it as `bun run scripts/test-free-shards.ts` from the repo root, or pass rootDir if calling collectFreeTestFiles programmatically.
  2. Verify the three test roots exist: ls browse/test test make-pdf/test — if any is missing, the checkout is incomplete or the repo layout changed.
  3. If the layout changed, update the TEST_ROOTS constant at line 32 to the new paths, or pass a custom rootDir to the exported collectFreeTestFiles(rootDir).
  4. Run with --list after fixing the path; if --list prints files but main() still throws, the issue is that collectFreeTestFiles is being called with a wrong rootDir in your integration.
  5. If all files are being excluded as paid-eval, inspect PAID_EVAL_TESTS (lines 37-44) — a too-greedy regex may be over-matching your test filenames.

Example fix

// before: run from a subdirectory, ROOT resolves wrong
# (cwd is browse/)
bun run ../scripts/test-free-shards.ts
// after: run from repo root
bun run scripts/test-free-shards.ts --list

// programmatic fix: pass the correct root explicitly
import { collectFreeTestFiles } from './scripts/test-free-shards.ts';
const files = collectFreeTestFiles('/abs/path/to/repo/root');
Defensive patterns

Strategy: validation

Validate before calling

// Verify the three test roots exist and contain at least one *.test.* file before
// invoking main(), so the failure is reported as a path issue, not a surprise throw.
import * as fs from 'fs';
import * as path from 'path';
const ROOTS = ['browse/test', 'test', 'make-pdf/test'];
const missing = ROOTS.filter(r => !fs.existsSync(path.join(process.cwd(), r)));
if (missing.length === ROOTS.length) {
  console.error(`No test roots found under cwd ${process.cwd()}. Expected: ${ROOTS.join(', ')}`);
  process.exit(2);
}
// Or call the exported function directly and react to empty:
import { collectFreeTestFiles } from './scripts/test-free-shards.ts';
if (collectFreeTestFiles().length === 0) {
  console.error('No free test files; check TEST_ROOTS and PAID_EVAL_TESTS in the script.');
  process.exit(2);
}

Type guard

// Guard the directory-listing boundary the script itself does not guard.
function hasTestFiles(dir: string): boolean {
  if (!fs.existsSync(dir)) return false;
  // walkTestFiles is private; approximate with a shallow + recursive check via the
  // exported collectFreeTestFiles(rootDir) instead for full fidelity.
  return collectFreeTestFiles(path.dirname(dir)).length > 0;
}

Try / catch

// Treat empty discovery as a configuration error (exit 2), not a test failure.
if (import.meta.main) {
  try { process.exitCode = main(); }
  catch (err) {
    const msg = (err as Error).message;
    if (msg.startsWith('No free test files')) {
      console.error('[test-free-shards] discovery failure — verify cwd is repo root and test/ trees exist');
      process.exitCode = 2;
    } else { console.error(msg); process.exitCode = 1; }
  }
}

Prevention

When it happens

Trigger: Running the script from a directory that is not the repo root, so path.join(rootDir, 'browse/test') etc. point at non-existent paths (fs.existsSync at line 160 silently skips them). Running in a worktree or checkout where the test/ trees were not copied. Running after a monorepo restructure that moved browse/test or make-pdf/test. Running on a fresh export that stripped .test.ts files. Or, less likely, every test file matches one of the PAID_EVAL_TESTS regexes (e.g. a checkout containing only security-review-fullstack.test.ts).

Common situations: CI checked out into a subdirectory and invoked the script with a relative cwd. A contributor runs `bun run scripts/test-free-shards.ts` from inside browse/ rather than the repo root. A release archive excluded test directories. The repo was cloned with --depth or a sparse-checkout that omitted test/. A rename of browse/test → browse/tests (plural) was not reflected in the TEST_ROOTS constant.

Related errors


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