Learn why TypeScript throws TS5058 when tsc --project points at a path that is not there, and how to fix the working directory in npm scripts and CI.
TS5058 means tsc was handed a --project path and found nothing there. You ran something like tsc -p packages/api/tsconfig.json or tsc --project tsconfig.build.json, and when the compiler asked the filesystem for that path, the answer was "no such file or directory". It stops immediately and reports the path back to you, quoted exactly as it received it.
The check happens before anything else: before the config is parsed, before a single source file is read, before any type checking. That is why the message has no file and no line number attached, and why none of your TypeScript code can be responsible for it. This is a command-line error, not a type error.
error TS5058: The specified path does not exist: 'packages/api/tsconfig.json'.
~~~~~~~~~~~~~~~~~~~~~~~~~~
resolved against the current
working directory, not the scriptThe detail that trips almost everyone up is in that underline. The -p argument is resolved against the current working directory of the tsc process — not the directory containing the package.json whose script you ran, and not the directory containing the config file. Paths inside the config (include, exclude, outDir, extends) are resolved relative to the config file; the path to the config is not. A command that is correct from the repository root is wrong from inside a package, and vice versa.
Two neighbouring diagnostics mean something more specific, so read the code before you start editing paths. TS5057 — "Cannot find a tsconfig.json file at the specified directory" — means the directory is really there but holds no tsconfig.json. TS5083 — "Cannot read file" — means the path resolved but the file could not be read, which is what you get when an extends target is missing. TS5058 is the blunt one: nothing exists at that path at all.
This is the overwhelming majority of real TS5058 reports. A monorepo has a build script written from the root's point of view, and then something runs it from inside the package — npm run build executed in packages/api, a CI job with a working-directory: key, a task runner that sets its own cwd per package.
// ❌ Broken — packages/api/package.json, path written as if from the repo root
{
"name": "@acme/api",
"scripts": {
"build": "tsc -p packages/api/tsconfig.json"
}
}$ cd packages/api && npm run build
error TS5058: The specified path does not exist: 'packages/api/tsconfig.json'.// ✅ Fixed — paths are relative to the package that owns the script
{
"name": "@acme/api",
"scripts": {
"build": "tsc -p tsconfig.json"
}
}The rule that keeps this straight: a script's paths belong to the package.json that declares it, because npm sets the cwd to that package's directory before running it. If the root needs to build the package, it should delegate rather than reach in — npm run build -w packages/api runs the workspace's own script in the workspace's own directory, and the root never has to know how the package lays out its configs.
The second most common cause is simply that the file is not called what the script says. Someone standardised tsconfig.prod.json to tsconfig.build.json, or moved configs into a config/ folder, and one npm script or one CI step kept the old spelling.
// ❌ Broken — package.json still references the pre-rename name
{
"scripts": {
"build": "tsc -p tsconfig.prod.json"
}
}[object Object]// ✅ Fixed — the name that is actually on disk
{
"scripts": {
"build": "tsc -p tsconfig.build.json"
}
}Before changing anything, run ls tsconfig*.json from the directory the script runs in. It takes a second and it answers both questions at once: whether the file exists under a different name, and whether you are even in the right directory to see it. Searching the repository for the old name afterwards (grep -r tsconfig.prod.json .) usually turns up two or three more stale references in CI workflows and Dockerfiles.
A build that is green locally and red in CI, with the path spelled correctly in both, usually means the file is not in the environment that failed. The classic version is a Docker build where .dockerignore or a narrow COPY left the config out of the image, but a .gitignored tsconfig.local.json produces exactly the same error on a fresh clone.
# ❌ Broken — only the sources and package.json are copied in
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
COPY src ./src
RUN npm ci && npx tsc -p tsconfig.build.json[object Object]# ✅ Fixed — copy the config the build step actually needs
FROM node:22-alpine
WORKDIR /app
COPY package*.json tsconfig.json tsconfig.build.json ./
COPY src ./src
RUN npm ci && npx tsc -p tsconfig.build.jsonTo confirm the diagnosis rather than guess at it, make the failing environment list what it has. Adding RUN ls -la immediately before the tsc line, or an ls -la step before the build step in CI, turns a one-line error into a definitive answer. Check .dockerignore too — a broad *.json or config* pattern excludes the config even when the COPY instruction names it.
Because the compiler prints the path exactly as it received it, TS5058 is unusually good at exposing shell-quoting mistakes. If the quoted path in the message contains a $, a %, or a literal ${{ ... }}, the path was never expanded — and if a segment is missing entirely, the variable expanded to an empty string.
// ❌ Broken — single quotes stop the shell from expanding the variable
{
"scripts": {
"build": "tsc -p '$CONFIG_DIR/tsconfig.json'"
}
}[object Object]// ✅ Fixed — no variable to get wrong, and the same on every machine
{
"scripts": {
"build": "tsc -p config/tsconfig.build.json"
}
}The general lesson is to read the quoted path in the message as data, not as decoration. It is the literal string that reached the compiler after every layer of shell, npm and CI templating has had its turn, and comparing it character by character with what you expected identifies the layer that mangled it. Hard-coding a repository-relative path, as above, removes a whole class of these failures; where a variable genuinely is needed, double-quote it so it expands.
Read the quoted path, then ls it from the directory the command actually runs in. The message gives you the exact string tsc tried. Reproduce the cwd — cd packages/api if that is where npm put you — and run ls -la <that path>. Either the file appears, in which case the problem is elsewhere in the chain, or it does not, and you have confirmed the cause in one command instead of theorising about it.
Write the path relative to the package.json that owns the script. npm runs a script with the cwd set to the directory of its own package.json, so tsc -p tsconfig.json is correct inside a workspace package even when the root would need packages/api/tsconfig.json. If you need the root to trigger the build, delegate with npm run build -w packages/api rather than pointing a root script into a package's internals.
Point -p at the config file, not at its directory, when you are unsure. tsc -p packages/api only works if packages/api/tsconfig.json exists; if the directory is there but the config is not, the error changes to TS5057. Naming the file explicitly — tsc -p packages/api/tsconfig.build.json — makes the command unambiguous and makes a wrong path fail with the clearer diagnostic.
When only CI or Docker fails, fix the environment, not the path. A path that is correct on your machine and missing in the image means the file was never copied or was ignored. Check .dockerignore and the COPY instructions, check .gitignore for configs that only exist locally, and add a temporary ls -la step to the failing job so the next person does not have to guess.
Do not paper over it with cd chains or an absolute path. cd packages/api && tsc -p tsconfig.json works until a script is called from somewhere new, and /Users/you/work/api/tsconfig.json breaks for every colleague and every CI runner. Keep each script's paths relative to its own package, let the workspace tooling handle cross-package builds, and check the command into version control so the working directory is never an unwritten assumption.
The path you gave to tsc -p or tsc --project does not exist. The compiler resolves that argument against the current working directory of the process — not the directory of the npm script, and not the directory of the config file — then asks the filesystem for it and stops when the answer is "nothing there". The most common trigger is a monorepo script whose path was written from the repository root but which is executed from inside a package. Because the check runs before the config is parsed, nothing in your TypeScript code or your compiler options can cause or cure it.
They mark two different stages of the same lookup. TS5058, "The specified path does not exist", means the filesystem has nothing at the path — a typo, a stale name, or a wrong working directory. TS5057, "Cannot find a tsconfig.json file at the specified directory", means the directory really is there but contains no tsconfig.json, which is what you get from tsc -p ./packages/web when that package has not been configured yet. A third neighbour, TS5083 ("Cannot read file"), appears when a path resolves but cannot be read, most often a missing extends target. Treat the code as a hint about which of the three to check first.
To the current working directory. Only the -p argument itself behaves this way; every path inside the config — include, exclude, files, outDir, rootDir, extends, references — is resolved relative to the config file that contains it. That asymmetry is what makes the error feel inconsistent: moving a config and updating its internal paths can leave the command that invokes it broken, and moving the invocation can break it without the config changing at all. If you want to see which directory a script is really starting from, prefix it temporarily with pwd && and run it the way CI does.
Browse all TypeScript practice challenges to keep sharpening your type-level skills.
Track your progress through 200+ hands-on challenges. Free, sign in with GitHub.
Or start solving right away: explore all TypeScript challenges