denoland/deno · error · anyhow::Error
failed to run native tsc
Error message
failed to run native tsc: {e} What it means
`deno check` type-checks with a native (Go) tsc binary that Deno downloads on demand and caches under DENO_DIR (ensure_native_tsc, cli/tsc/native.rs:78). run_native_tsc spawns that cached binary with `--project <tsconfig> --noEmit --pretty false --diagnostics`; this error (native.rs:234) wraps an OS-level spawn failure — the process never started. It is distinct from TypeScript diagnostics, which are parsed separately.
Solutions
- Delete the cached native tsc so it re-downloads: remove the tsc cache directory under the DENO_DIR location printed by `deno info`, then re-run `deno check`.
- Check exec permission and disk space on the cached binary path shown in Deno's cache.
- If security software quarantined it, allowlist the DENO_DIR location.
- Persist after a clean re-download? Report with the spawn error suffix ({e} names the io::Error kind).
Example fix
# before
$ deno check .
error: failed to run native tsc: No such file or directory (os error 2)
# after — purge the broken cache and re-run
deno_dir=$(deno info 2>/dev/null | sed -n 's/^DENO_DIR: //p')
rm -rf "$deno_dir/tsc" 2>/dev/null || rm -rf "${DENO_DIR:-$HOME/.cache/deno}/tsc"
deno check . Defensive patterns
Strategy: validation
Validate before calling
# Verify the cached native tsc binary exists and is executable before `deno check`
deno_dir=$(deno info 2>/dev/null | sed -n 's/^DENO_DIR: //p')
bin=$(find "${deno_dir:-${DENO_DIR:-$HOME/.cache/deno}}" -type f -name 'tsc' -perm -u+x 2>/dev/null | head -1)
[ -n "$bin" ] || echo 'native tsc missing/non-executable — expect spawn failure'; deno check . Prevention
- Allowlist the DENO_DIR cache in antivirus/EDR so the downloaded tsc binary is not quarantined.
- After OS/arch migrations or deno clean, run a trivial `deno check` early to re-download the toolchain.
- Keep disk space and process limits healthy — spawn-time ENOMEM/EPERM surfaces as this same error.
When it happens
Trigger: `deno check` when the cached tsc binary was deleted, moved, or quarantined by antivirus between the exists() check and the spawn; exec permission lost on the binary; out-of-memory / fork limits at spawn time; arch-mismatched binary after an OS migration.
Common situations: Antivirus/EDR removing little-known downloaded binaries; `deno clean` or manual DENO_DIR surgery racing a check; containers with low PID/memory limits; shared caches across machines.
Related errors
- native tsc exited with
- Can't convert url (" ") to filename.
- Can't convert url (" ") to filename.
- Cannot generate declarations for files with different…
- Cannot get 'stderr': 'stderr' is not piped
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/ded367599e7adf80.
Report an issue: GitHub.
Appendix: 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 9ad36f7a2c)