Fix subprocess.CalledProcessError: Command Returned Non-Zero Exit Status in Python

intermediate🐍 Python2026-03-26| Python 3.x, Linux / macOS / Windows, any shell command execution via subprocess module

Error Message

subprocess.CalledProcessError: Command '['...']' returned non-zero exit status 1
#python#subprocess#shell#process

The Error

You called a shell command from Python using subprocess.run(), subprocess.check_call(), or subprocess.check_output() and got:

subprocess.CalledProcessError: Command '['git', 'push']' returned non-zero exit status 1

This happens when the command exited with a non-zero status code β€” it failed. Python raises CalledProcessError only when you explicitly ask it to check for failure: either via check=True, or by using check_call/check_output.

Why It Happens

Every process exits with a status code. Zero means success; anything else means failure. By default, subprocess.run() quietly returns a result object even when the command fails β€” no exception raised. Pass check=True, and Python turns a non-zero exit code into a CalledProcessError.

Typical triggers:

  • The command itself failed (e.g., git push rejected, pip install package not found)
  • Wrong arguments passed to the command
  • Missing dependency or binary not in PATH
  • Permission denied on a file or directory
  • Script explicitly calls sys.exit(1) or exit 1

Step 1 β€” Read the Actual Error Output

Before anything else: read what the command printed. Subprocess captures nothing by default β€” output goes straight to the terminal and disappears. Capture it like this:

import subprocess

result = subprocess.run(
    ['git', 'push'],
    capture_output=True,
    text=True
)
print('STDOUT:', result.stdout)
print('STDERR:', result.stderr)
print('Exit code:', result.returncode)

Nine times out of ten, the real error message is sitting in stderr. Read it β€” most fixes become obvious at this point.

Step 2 β€” Catch the Exception Properly

When using check=True, wrap the call in a try/except to handle failures without crashing:

import subprocess

try:
    result = subprocess.run(
        ['git', 'push'],
        check=True,
        capture_output=True,
        text=True
    )
    print(result.stdout)
except subprocess.CalledProcessError as e:
    print(f'Command failed with exit code {e.returncode}')
    print(f'STDOUT: {e.stdout}')
    print(f'STDERR: {e.stderr}')

The exception object e carries .returncode, .stdout, and .stderr. Those three attributes tell you exactly what went wrong.

Step 3 β€” Fix Based on the Root Cause

Command not found

Stderr says command not found or No such file or directory? The binary isn't in PATH. Check first before running:

import shutil

if not shutil.which('mycommand'):
    raise EnvironmentError('mycommand is not installed or not in PATH')

Or skip the lookup entirely and use the full path:

subprocess.run(['/usr/local/bin/mycommand', '--flag'], check=True)

Shell features (pipes, redirects, wildcards)

Passing a list like ['ls', '-la', '|', 'grep', 'txt'] doesn't work β€” the pipe character is treated as a literal argument, not a shell operator. Use shell=True with a string instead:

# Wrong β€” | is not an argument
subprocess.run(['ls', '-la', '|', 'grep', 'txt'], check=True)

# Correct
subprocess.run('ls -la | grep txt', shell=True, check=True)

One hard rule: never pass user-supplied strings to shell=True. That's a shell injection waiting to happen. Stick to hardcoded strings or sanitized input.

Working directory issues

Commands like npm install or make need to run from a specific directory. Use cwd instead of os.chdir():

subprocess.run(['npm', 'install'], check=True, cwd='/path/to/project')

Missing environment variables

Some scripts rely on env vars that aren't set in the subprocess environment. Pass them explicitly:

import os

env = os.environ.copy()
env['MY_VAR'] = 'value'
subprocess.run(['myscript.sh'], check=True, env=env)

Permission denied

Script not executable? Two options. Either add the execute bit first:

subprocess.run(['chmod', '+x', 'myscript.sh'], check=True)
subprocess.run(['./myscript.sh'], check=True)

Or skip the permissions issue entirely and invoke the interpreter directly:

subprocess.run(['bash', 'myscript.sh'], check=True)

Step 4 β€” Decide: check=True or Manual Check?

Not every failure deserves an exception. When a non-zero exit code is a normal outcome β€” checking connectivity, testing if a process is alive β€” just inspect returncode directly:

result = subprocess.run(['ping', '-c', '1', '8.8.8.8'], capture_output=True)
if result.returncode == 0:
    print('Host is reachable')
else:
    print('Host unreachable')

Reserve check=True for commands where failure should stop execution. Skip it when failure is a valid path.

Verify the Fix

Run the call in isolation and confirm the exit code comes back as 0:

result = subprocess.run(
    ['your', 'command', 'here'],
    capture_output=True,
    text=True
)
print('Exit code:', result.returncode)  # Should print: Exit code: 0
print(result.stdout)

Exit code 0 β€” you're done. Still non-zero β€” check result.stderr for the next clue and repeat.

Quick Reference

  • Capture stderr first β€” capture_output=True, text=True reveals the real failure reason
  • Use check=True for critical commands where failure should halt execution
  • Drop check=True when a non-zero exit code is an expected, valid result
  • Never combine shell=True with untrusted input β€” pass a list of arguments instead
  • Test the command in your terminal first to rule out environment and PATH issues

Related Error Notes