Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
CLI · sf

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/path but 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:

  1. Always cd to the project root before running sf commands.
  2. Use the sfdx-project.json file's presence as confirmation that you're at the right level.
  3. 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:

  1. Verify the file exists: ls -la <path>.
  2. Verify your working directory: pwd.
  3. Combine them: from pwd, can you cat <path> and see the file?
  4. Try the absolute path: realpath <path> resolves to the full path; pass that to the CLI.
  5. If all else fails, run the CLI with --verbose to 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.xml and 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

Share on LinkedInShare on X

Related Salesforce errors