#3116

Pass arguments through scriptc run and shorten the CLI's errors

Opencramforcewants to merge intofrom

Four first-contact defects in the scriptc command line, each reproduced on main (714482d49) before the change. The compiler and the runtime are untouched: only the CLI's argument handling, its error text and its wording about Node islands change.

1. scriptc run dropped the program's arguments

scriptc run args.ts a b printed [] for process.argv.slice(2) and exited 0.

run now follows node and tsx: everything after the entry file is the program's, verbatim, including options and a --. scriptc's own options go before the file, and a -- before the file ends them.

commandbeforenownode args.ts … and tsx args.ts …
scriptc run args.ts a b[]["a","b"]["a","b"]
scriptc run args.ts -- a b --flag[]["--","a","b","--flag"]["--","a","b","--flag"]
scriptc run args.ts a b --flag -- cUnknown option '--flag' and the help["a","b","--flag","--","c"]["a","b","--flag","--","c"]

This changes one existing spelling: scriptc run file.ts --strip used to strip, and now passes --strip to the program. Write scriptc run --strip file.ts. The help line is now scriptc run [options] <file.ts|.js> [args]. build and coverage still take options anywhere. The tests and the Windows CI smoke that used the old order are updated here.

process.argv keeps the shape the limitations page documents: argv[0] is "scriptc", argv[1] is the executable's path, and arguments start at index 2, as in Node.

One scanner (programArgumentsStart, driven by CLI_OPTIONS) finds where the program's arguments begin, for both the full CLI and the bootstrap's cached route. CliHost.run takes the arguments, the WASI runner forwards them, and the scriptc_run MCP tool gains an args list.

Exit status and signals

Exit codes already passed through. Two things did not:

  • A program killed by a signal made run exit 1. It now exits 128 plus the signal's number, as tsx does and as a shell reports for node. The line scriptc: program killed by SIGSEGV stays, except when run itself was sent the signal, so Ctrl-C ends quietly.
  • run had no signal handlers, so kill, a timeout, or a process manager stopped scriptc and left the program running. SIGTERM and SIGHUP now go to the program, and SIGINT too unless a terminal already delivered it to the whole foreground group; run then ends with the program's status.

The Node-hosted CLI, the bootstrap and the native CLI now share one implementation (cli/run-program.ts), which moves the native CLI's run from spawnSync to spawn.

2. A missing entry file printed a compiler stack trace

scriptc run nope.ts printed 17 lines ending in at loadProgram7 (…/frontend/program.js:867:19). run, build and coverage now print one line and exit 1:

scriptc: entry file not found: /tmp/cli-repro/nope.ts

A directory gets scriptc: entry is not a file: <path>.

3. A usage error printed 198 lines

Every usage error appended the whole help, so the message scrolled away. All 49 of them now print the message and one pointer line. An unknown option also names the nearest valid one, and an unknown command names the nearest command or, for a source file, the run spelling:

$ scriptc build args.ts --optimisation dev
scriptc: Unknown option '--optimisation'
  did you mean '--optimization'?
Run 'scriptc --help' for the commands and options.
$ scriptc args.ts
unknown command "args.ts"
  to compile and run a file: scriptc run args.ts
Run 'scriptc --help' for the commands and options.

4. Stale wording about Node islands

--help said --node-islands, --node-island, --native and --node were "REPORT-ONLY … builds compile as before", and the coverage report headed its section "would-be, report-only: the build is unchanged". Builds run island packages in Node by default and print SC8001 notes saying so.

  • The option help now says what a build does under each policy.
  • The coverage header reads Node islands (a build runs these in Node where it cannot compile them natively; --node-islands=auto). The qualifier is deliberate: the section is the detector's list, and it can name a package the build still compiles natively. tests/coverage-fixtures/node-islands lists four packages there, and a build of it islands one.
  • --node <path> is parsed and stored, and nothing reads it. Its help now says so instead of describing an effect.
  • The JSON envelope's nodeIslands.mode: "would-be" is a schema field and is left alone.

Not changed: run prints no build warnings

run shows none of the divergence warnings, SC6101 or SC8001 notes that build shows. That is intended (command.ts: "run keeps stderr for the program's own output") and it keeps run's output comparable with node's, so it stays. The limitations page and the CLI page now state it.

Proof

All runs are in Vercel Sandboxes on Intel Xeon @ 2.90GHz. The gate and the bootstrap ran on this change at 933688a07 (base 714482d49); the PR head is the same commit rebased onto main, whose newer commits touch none of these files.

  • Reproduction on main (714482d49): each of the four defects, with node 24.22.1 and tsx for comparison.
  • New tests: packages/cli/test/run-arguments.test.ts (10 tests) runs the CLI as a process: arguments against node for six argument lists on the first build and on the bootstrap's cached route, the process.argv shape, options before the entry and --, exit codes, 128+N for a signal death, SIGTERM and SIGINT reaching the program, the missing entry for run, build and coverage, and the text of twelve usage errors. packages/compiler/src/cli/usage.test.ts (5 tests) covers the scanner. The MCP tests cover the args list, and the native-driver bootstrap test now runs a program with arguments through the native CLI.
  • Focused tests: packages/cli/test, packages/compiler/src/cli, tests/harness/static-only.test.ts and tests/harness/coverage.test.ts pass, with one exception that is not this change: coverage.test.ts "every corpus program is 100% static" hit its 600 s limit twice when run unsharded on 8 vCPUs (CI shards it; it calls analyze, not the CLI).
  • A race found by those tests: with the listeners installed after the program started, a SIGINT sent as soon as the program printed its first line ended scriptc alone and left the program running, in 2 of 40 rounds. Listening before the start: 450 of 450 rounds (150 each for SIGINT, SIGTERM and SIGHUP) end with the program's status.
  • Zero-cost gate (bin/validate --skip=lanes,bench,t262,san-bootstrap): PASS. 3807 programs identical, 68 fail on both sides, no differing row and no runtime-only row. Compile time within tolerance: bench geomean −0.45% [95% CI −1.22%, +0.92%], self −0.12% [−1.55%, +0.29%], fixtures −0.05% [−0.34%, +0.24%]. The candidate's compiler source builds with the candidate.
  • Native bootstrap (tests/harness/self-hosting-native-driver.test.ts, plain, 32 vCPU): passes in 2125 s (seed build 785 s, seed command and library probes 372 s, self-rebuild 666 s, Node and native IR and LLVM compared, rebuilt compiler emits itself 672 s).
  • Self-hosting IR check: scriptc build packages/compiler/src/native/cli.ts --emit=ir exits 0 with no error SC line.
  • Docs gate (NEXT_DIST_DIR=.next-check pnpm check in docs/): passes, 25 route tests. The three console blocks changed in the docs are real output of this build.
  • pnpm lint and pnpm format:check: clean.

The sanitized bootstrap was not run: the change adds no lowering, record, object or closure mechanism, and CI runs it.

🤖 Generated with Claude Code

Activity

5
vercel[bot]6d ago

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated
scriptc ReadyPreview, v0Nov 7, 2026 7:10am UTC
vercel[bot]reviewed6d ago
cramforcereviewed6d ago