Linux / shell

command not found — when the same command works in your terminal

Written and reviewed by Sahil Srivastav

Shell environmentPATHDeployment
deploy.sh: line 4: node: command not found

What this error actually means

A command without a slash must be resolved by the shell. Your terminal may resolve it to a function, alias or executable found through PATH. A scheduled job starts with its own environment and shell startup rules, so the same spelling does not guarantee the same command. The failure happens before the intended program runs; changing that program’s configuration cannot repair command lookup.

Runtime managers make this especially confusing. An interactive startup file may define a function that activates a particular Node or Python installation and prepends its bin directory. The scheduler does not inherit a future terminal session’s activation. Installing the runtime again can leave two installations while the service still searches neither of them.

Treat the launcher, effective user, interpreter, working directory and PATH as inputs to the script. Compare those inputs between successful and failing executions. Avoid assuming every scheduler has the same default PATH or that a non-interactive shell necessarily reads your personal startup file.

Causes, most common first

  1. 1The executable directory is missing from the job’s PATH. User-local installations and runtime-manager versions often live outside the system directories provided by a launcher. Check the exact installed path before editing PATH; adding a guessed directory hides the real packaging problem.
  2. 2The interactive command is a function or alias. There may be no executable with that name. A function defined in your shell configuration is not automatically available to a new shell. Shell-specific activation commands also fail when the job uses a different interpreter.
  3. 3The job runs as a different user or in a different directory. Relative paths such as ./tools/build depend on the working directory. A service account may also lack traversal permission to a runtime under a personal home directory. Neither issue is fixed by copying the interactive user’s entire environment.
  4. 4The script replaces PATH accidentally. An assignment such as PATH=/opt/app/bin discards system commands needed later. A literal tilde stored in a variable, or a quoted variable name instead of its value, can introduce an entry that looks plausible in logs but names no directory.

When you see it

  • A deployment succeeds over SSH but fails from cron or a service unit
  • A runtime manager command works at a prompt but is absent inside the job
  • Running the script as your own user succeeds while its service account fails
  • An absolute executable path works even though the bare command name does not

How to diagnose it

Step 1

Identify what succeeds in the terminal

In Bash, type -a lists matching functions, aliases and executable paths. If the first result is a function, inspect the activation step rather than looking only for a binary. Run the equivalent lookup inside the failing script as well.

type -a node
command -v node

Step 2

Capture only the relevant job inputs

Temporarily put these lines immediately before the failing command. They report the actual identity, working directory and search path without dumping the whole environment, which may contain credentials. Compare the output from the real launcher.

id
pwd
printf 'PATH=%s\n' "$PATH"
command -v node

Step 3

Reproduce with a deliberately small environment

Run this as the job’s user and from its configured directory. It is a useful isolation experiment, not an exact emulation of every scheduler. If it fails while your terminal succeeds, enumerate the specific missing input instead of sourcing all of .bashrc.

env -i PATH=/usr/bin:/bin /bin/bash --noprofile --norc ./deploy.sh

Step 4

Inspect the service’s declared execution context

For a systemd unit named app.service, these properties show the effective launch command, account and directory. Inspect the unit configuration for its environment separately. A successful lookup in an administrator’s login shell says nothing about this context.

systemctl show app.service -p User -p WorkingDirectory -p ExecStart

The fix

Make runtime selection part of deployment. Install a known runtime into a service-accessible location, then use its absolute path or an explicitly configured PATH. Keep that path consistent with the version used to build dependencies. Do not point a production service at whichever version happens to be active in a developer’s terminal.

Move required initialisation into a small, non-interactive launcher with a declared interpreter. Set the working directory explicitly and fail if it cannot be entered. If a runtime manager is unavoidable, invoke its documented non-interactive activation path rather than importing an entire login configuration with prompts and unrelated side effects.

The example assumes Node was deliberately installed at /opt/node/bin/node. Substitute the verified installation path. Validate command lookup before the deployment does any work, so a missing dependency produces one clear failure instead of a partially completed release.

#!/bin/bash
export PATH=/opt/node/bin:/usr/bin:/bin
cd /srv/app || exit 1
command -v node >/dev/null 2>&1 || {
  printf 'Node runtime missing from service PATH\n' >&2
  exit 127
}
exec node server.js

How to stop it coming back

  • Exercise the deployment under its real service account with a minimal environment before release.
  • Keep executable versions, working directory and required environment in the service definition or launcher, with a single owner for each setting.
  • Use shell functions for interactive convenience, but give automation an executable entry point with a stable contract.

Practise production debugging in a real repository

Reading about a failure and reproducing one are different skills. Gronex ships broken backend repositories with failing test suites that encode the real invariant, so you debug from evidence instead of memorising symptoms.

FAQ

Should I add the current directory to PATH?

Use ./tool for a deliberate local executable instead. Adding the current directory changes which program a bare name may resolve to whenever the job changes directories. It does not repair a missing runtime installation or a service-account permission problem.

Why does bash script.sh behave differently from ./script.sh?

The first explicitly chooses Bash and reads the file as input. Direct execution follows the shebang and requires execute permission. If your script depends on Bash, declare it correctly and ensure that interpreter exists in the deployment environment.

Does exit status 127 prove PATH is the problem?

It is a useful clue for a missing command, but identify which command emitted it. A wrapper may successfully start and then fail to locate a nested helper. Capture the failing line and run lookup from that same context.

Related

Other errors engineers hit next to this one

Full error and symptom index →