ENOENT: no such file or directory
The Salesforce CLI couldn't find a file or directory you referenced. Almost always a working-directory mismatch — the CLI is running in `/some/path` but the file path you passed is relative to where you *thought* you were.
Also seen asENOENT·no such file or directory·ENOENT: no such file or directory·sf cli ENOENT
You type sf project deploy start --manifest manifest/package.xml and the CLI returns ENOENT: no such file or directory, open 'manifest/package.xml'. You're sure the file exists; you can see it in your editor. The CLI is running from somewhere else. Your terminal's working directory and the path you typed don't match.
What ENOENT actually means
ENOENT is a Unix system error meaning "Error No Entry": the file or directory at the specified path doesn't exist at the location the process is looking. Node.js (the runtime behind the Salesforce CLI) bubbles this error up unchanged, so it appears in the CLI output verbatim.
The error has nothing to do with permissions, file content, or Salesforce-side issues. It's a "the file at this path doesn't exist" message. The cause is almost always:
- Working directory mismatch. The CLI is running in
/some/pathbut the file path you passed is relative to where you thought you were. - Typo in the path. A wrong character in the filename or a missing folder.
- The file genuinely doesn't exist. Either it wasn't created, it was deleted, or it's in a different branch you haven't checked out.
The broken example
A developer running the CLI from a subdirectory:
cd force-app/main/default/classes
sf project deploy start --manifest ../../../manifest/package.xml
The relative path looks correct, but the CLI is searching from the current directory. The ../../../manifest/ path needs to be evaluated against force-app/main/default/classes, which resolves to force-app/main/default/classes/../../../manifest/ = force-app/manifest/. The file isn't there; it's at the project root's manifest/.
The fix: either cd back to the project root or use an absolute path.
The fix: confirm working directory and use absolute paths
pwd
# /home/dev/sfdx-project/force-app/main/default/classes
# Either cd to the project root:
cd /home/dev/sfdx-project
sf project deploy start --manifest manifest/package.xml
# Or use an absolute path:
sf project deploy start --manifest /home/dev/sfdx-project/manifest/package.xml
The absolute path eliminates any ambiguity about resolution. Relative paths work, but require knowing your current working directory.
A practical pattern for Salesforce DX commands
Most Salesforce CLI commands expect to run from the project root (the directory containing sfdx-project.json). If you're not there, many commands fail or behave unexpectedly.
The discipline:
- Always
cdto the project root before runningsfcommands. - Use the
sfdx-project.jsonfile's presence as confirmation that you're at the right level. - For scripts and CI,
cd "$PROJECT_ROOT"at the start.
#!/bin/bash
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
cd "$PROJECT_ROOT"
sf project deploy start --manifest manifest/package.xml --target-org production
The git rev-parse --show-toplevel resolves to the git repo root, which is typically the same as the Salesforce DX project root. The script works regardless of the user's starting directory.
When the path is correct but ENOENT still fires
A few subtle cases:
Case-sensitivity differences. macOS and Windows filesystems are usually case-insensitive; Linux is case-sensitive. A path like manifest/Package.xml works on macOS but fails on Linux CI. Always match the on-disk casing exactly.
Trailing whitespace or hidden characters. A path copy-pasted from a doc sometimes includes a trailing space or a non-printing character. The shell sees the path including the extra characters and fails. Re-type the path manually.
Symlinks. A symbolic link to a file that no longer exists produces ENOENT when accessed. The link itself exists; what it points to doesn't. The fix is to remove the stale link or re-create the target.
Permissions on parent directories. A file might exist but a parent directory is not readable by the current user. The error is technically about access, not existence, but Node sometimes surfaces it as ENOENT. Check ls -la on each level of the path.
The fixed example
A robust deployment script:
#!/bin/bash
set -euo pipefail
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
cd "$PROJECT_ROOT"
MANIFEST_PATH="$PROJECT_ROOT/manifest/package.xml"
if [ ! -f "$MANIFEST_PATH" ]; then
echo "Error: manifest not found at $MANIFEST_PATH"
exit 1
fi
TARGET_ORG="${TARGET_ORG:-production}"
echo "Deploying to $TARGET_ORG..."
sf project deploy start \
--manifest "$MANIFEST_PATH" \
--target-org "$TARGET_ORG" \
--wait 60
The pre-flight check catches the ENOENT case with a clear error before invoking the CLI. The absolute path eliminates relative-path resolution ambiguity. The set -euo pipefail ensures the script exits on any error.
Common CLI commands and their path expectations
Each sf command has implicit assumptions about which directories should exist:
sf project deploy start --manifest X: expects X to be a file path.sf project deploy start --source-dir X: expects X to be a directory.sf project retrieve start --manifest X: same as deploy.sf data import bulk --file X: expects X to be a CSV or similar.sf data export tree --output-dir X: expects X to be a writable directory.
If the path argument is wrong shape (file vs directory), the error message sometimes says ENOENT even when the actual issue is type. Verify both existence and type.
Cross-platform considerations
Windows uses backslashes in paths, but most CLI tools accept forward slashes too. The sf CLI normalizes paths internally, so:
sf project deploy start --manifest manifest/package.xml # works on all platforms
sf project deploy start --manifest manifest\package.xml # Windows-specific
For scripts that run on multiple platforms, prefer forward slashes everywhere.
PowerShell on Windows has additional quirks: paths with spaces require quoting, and some commands prefer single vs double quotes. Test scripts on the target platform before deploying widely.
A subtle case: shell expansion
A path with shell-special characters can be expanded before the CLI sees it:
sf project deploy start --manifest manifest/*.xml
The shell expands *.xml to a list of matching files. If multiple match, the CLI receives multiple arguments and fails. If none match, the CLI receives the literal *.xml and ENOENTs because no such file exists.
The fix: quote the path or use the explicit name.
sf project deploy start --manifest manifest/package.xml
Manifest auto-discovery
If you omit the --manifest flag, the CLI looks for manifest/package.xml in the project root by convention. Running from a subdirectory and omitting the flag fails because the convention path is relative to the current directory:
cd force-app/main/default/classes
sf project deploy start # ENOENT
The CLI looks for force-app/main/default/classes/manifest/package.xml, which doesn't exist. Either cd to the project root or pass the manifest path explicitly.
A diagnostic command worth knowing
When debugging path issues, the verbose CLI output shows exactly where it looked:
sf project deploy start --manifest manifest/package.xml --verbose
The verbose output includes the resolved paths and any intermediate steps. The first error usually has a clear "looked at X, did not find Y" message.
For even more detail, set SF_LOG_LEVEL=trace in your environment. The CLI then logs every internal step.
Recovery patterns
When ENOENT fires unexpectedly during a deploy:
- Verify the file exists:
ls -la <path>. - Verify your working directory:
pwd. - Combine them: from
pwd, can youcat <path>and see the file? - Try the absolute path:
realpath <path>resolves to the full path; pass that to the CLI. - If all else fails, run the CLI with
--verboseto see the resolved path it tried.
The five-step check resolves most cases in under a minute.
When ENOENT appears in CI
CI systems clone the repository fresh on every run, then execute commands. If a CI step expects a file that wasn't committed to git (e.g., a generated package.xml that the script forgot to create), ENOENT fires.
The fix: either commit the file or add a generation step to the CI pipeline. Don't assume files exist that aren't tracked.
Files that have moved between releases
A common ENOENT story: a script references manifest/old-package.xml but the manifest was renamed to manifest/package.xml in a previous PR. The script wasn't updated. The next time it runs, ENOENT.
A discipline that prevents this: any time you rename a file, grep the entire repo for the old name. References in scripts, docs, READMEs, and CI configs all need updating. The grep takes 30 seconds; missing a reference produces a one-incident-per-month surprise.
The relationship to project.json
The sfdx-project.json file declares the project's source paths. The CLI uses it to know where source lives. If the file references a directory that doesn't exist (a misspelled package directory), commands that depend on it fail with ENOENT.
Audit sfdx-project.json after any source structure change. The directories listed in packageDirectories must exist on disk.
A subtle Node.js gotcha
The Node.js runtime that powers the CLI sometimes caches filesystem reads. After certain filesystem operations (delete + recreate), a subsequent access to the recreated file can briefly return ENOENT due to cache staleness. This is rare and usually resolves on the next attempt.
If you see ENOENT for a file you just created in the same script, add a brief sleep:
touch newfile.txt
sleep 0.5
sf project deploy start --manifest newfile.txt
The sleep is rarely necessary in modern Node, but for older versions or unusual filesystems, it can help.
Source-format vs metadata-format paths
Salesforce DX supports two source formats:
- Source format (modern): one file per metadata component, organized in folders like
force-app/main/default/classes/MyClass.cls. - Metadata format (legacy): zipped bundles with
package.xmland per-type folders.
Some sf commands accept paths in one format but not the other. ENOENT can fire when the command expects metadata format but you pass a source-format path (or vice versa). The CLI docs name which format each command expects.
For new code, source format is the default. Metadata format is mostly for legacy operations (deploys from older orgs, retrieves of managed packages).
A scriptable check before every CLI call
For shell scripts that run many CLI commands, a guard function avoids ENOENT surprises:
ensure_file() {
if [ ! -f "$1" ]; then
echo "Error: file not found at $1" >&2
exit 1
fi
}
ensure_file "$PROJECT_ROOT/manifest/package.xml"
sf project deploy start --manifest "$PROJECT_ROOT/manifest/package.xml"
The guard runs in microseconds. The error message is clearer than the raw ENOENT. The script halts at the first missing file instead of running half the commands.
A note on .gitignore and missing files
A file ignored by .gitignore won't be committed to the repo. If your CI clones the repo and expects the file to exist, ENOENT fires. The fix is either to commit the file (loosen .gitignore) or to generate it during CI (add a generation step).
Common offenders: build artifacts, environment-specific config, generated manifests. Each should either be tracked or generated, not assumed.
When the CLI is itself misconfigured
A rare case: the sf CLI's own installation is incomplete or corrupted. Some plugin commands depend on files that the installer should have created. If a plugin install was interrupted, the CLI might ENOENT on its own internal files.
The fix: reinstall the CLI. On most systems: npm install -g @salesforce/cli (or use the installer specific to your OS). The reinstall is non-destructive and usually quick.
Further reading from Salesforce
Related dictionary terms
Share this fix
Related Salesforce errors
ERROR running force:auth:web:login: Invalid login URL
CLI · sfThe Salesforce CLI couldn't authenticate against the URL you gave it. Either the URL is wrong (typo, missing https, hitting a non-Salesforce…
My Domain is required for this org / Lightning components require My Domain to be deployed
CLI · sfMany Salesforce features (Lightning components, Connected Apps, single sign-on, Communities) require My Domain to be set up in the org. New …
Source push not allowed in this org. Source tracking is not enabled.
CLI · sfYou ran `sf project deploy start` (or `sfdx force:source:push`) against an org that doesn't have source tracking — production, a fully-copie…
The resource ... is not allowed by API version <X>
CLI · sfYour sf CLI (or the metadata API call) is using an API version older or newer than what the requested resource supports. Either upgrade the …
@wire(getRecord, ...) requires the fields property
Lightning · LWCYou used `getRecord` from `lightning/uiRecordApi` without specifying which fields to fetch. LDS doesn't auto-fetch all fields like an old-sc…