denoland/deno · error · anyhow::Error
failed to run native tsc: {e}
Error message
failed to run native tsc: {e} What it means
After obtaining the native compiler, deno spawns it with cwd set to the project root and PWD pinned to the same directory. If the OS-level spawn itself fails — executable missing, not executable, or a fork/exec error — the io error is wrapped in this message.
Source
Thrown at cli/tsc/native.rs:234
.arg("--project")
.arg(tsconfig_path)
.arg("--noEmit")
.arg("--pretty")
.arg("false")
.arg("--diagnostics")
.current_dir(project_root)
// The native compiler is written in Go, whose `os.Getwd` trusts the `PWD`
// environment variable over `getcwd()`. `current_dir` calls `chdir` but
// does not update the inherited `PWD`, so a symlinked launch directory
// (e.g. `/tmp` -> `/private/tmp` on macOS) would leave `PWD` pointing at
// the symlink and make tsc report every path relative to it. Pin `PWD` to
// the same directory we chdir'd into.
.env("PWD", project_root)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await
.map_err(|e| anyhow::anyhow!("failed to run native tsc: {e}"))
}
/// A single `path(line,col): error TS####: message` line from `tsc --pretty
/// false`. Continuation lines (indented elaborations) are folded into the
/// preceding diagnostic's message.
static DIAGNOSTIC_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"^(?P<file>.+?)\((?P<line>\d+),(?P<col>\d+)\): (?P<cat>error|warning|message) TS(?P<code>\d+): (?P<msg>.*)$",
)
.unwrap()
});
/// The position-less form (`error TS####: message`), e.g. `TS18003` (no
/// inputs) or a `TS5###` config error.
static DIAGNOSTIC_NO_POS_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(?P<cat>error|warning|message) TS(?P<code>\d+): (?P<msg>.*)$")
.unwrap()
});View on GitHub (pinned to 89f33cbef2)
Solutions
- Delete the cached compiler directory ($DENO_DIR/tsc/<version>/<platform>) so the next run re-downloads and re-extracts it with correct permissions
- Check the binary is executable and DENO_DIR is not mounted noexec: `ls -l <tsc-path>` and inspect mount options
- Set DENO_TSC_BIN to a known-good tsc binary to bypass the cached copy
Example fix
# before: cached copy lost its exec bit ls -l "$DENO_DIR/tsc/7.0.2/linux-x64/lib/tsc" # missing x # after rm -rf "$DENO_DIR/tsc/7.0.2" deno check mod.ts # triggers a clean re-download
Defensive patterns
Strategy: retry
Validate before calling
# bash: verify the cached binary is executable before running deno tscbin="$DENO_DIR/tsc/7.0.2/$(case "$(uname -s)" in Linux) echo linux;; Darwin) echo darwin;; *) echo win32;; esac)-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')/lib/tsc" if [ -e "$tscbin" ] && [ ! -x "$tscbin" ]; then rm -rf "$(dirname "$(dirname "$tscbin")")"; fi
Try / catch
# bash: clear the cache and retry once on spawn failure if ! deno check mod.ts 2>err.log; then grep -q "failed to run native tsc" err.log && rm -rf "$DENO_DIR/tsc" && exec deno check mod.ts exit 1 fi
Prevention
- Never copy $DENO_DIR between machines without preserving exec bits
- Avoid mounting DENO_DIR noexec; check with `mount | grep $(dirname $DENO_DIR)`
- On spawn failures, deleting $DENO_DIR/tsc/<version> and retrying re-downloads cleanly
When it happens
Trigger: The tsc path exists in the cache but is not executable (lost +x bit, extracted from a zip), the file vanished between the existence check and spawn, or process creation fails (memory limits, container restrictions).
Common situations: Cache corruption; extracting the toolchain without preserving permissions; DENO_DIR on a noexec mount; tightly limited containers.
Related errors
- The bench name can't be empty
- Expected 'fn' field in the first argument to be a bench func
- BenchContext::end() has already been invoked
- Object is not a valid image or a path to an image. `Deno.jup
- Request url protocol must be 'http:' or 'https:': received '
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/ded367599e7adf80.
Report an issue: GitHub.