Jenkins Error: 'ERROR: script returned exit code 1' — Cause, Fix, and Troubleshooting Guide
Fix 'ERROR: script returned exit code 1' in Jenkins: read the real sh/bat failure, use set -eo pipefail, returnStatus, and catchError instead of masking it.
- #jenkins
- #ci-cd
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
A sh (or bat) step ran a command that exited non-zero, so Jenkins fails the stage and aborts the build. The message is generic on purpose — the interesting failure is in the lines above it:
+ npm run build
... (build output) ...
error TS2304: Cannot find name 'foo'.
ERROR: script returned exit code 1
This is not Jenkins malfunctioning. The sh step reports the exit status of the shell it ran, and any non-zero status fails the step. Your job is to find which command inside the step exited 1, decide whether that failure should fail the build, and either fix the underlying command or handle the exit code deliberately.
Symptoms
- A stage fails with
ERROR: script returned exit code 1immediately after ash/batstep. - The real error (compiler error, failing test, missing file) is in the console output just above.
- A multi-line
shstep fails even though the “important” command looks like it succeeded. - A command that returns a meaningful non-zero code (like
grep,diff,test) fails the build unintentionally. - The pipeline is red but you want it merely
UNSTABLE, or you want to keep going. - Works locally, fails in Jenkins — usually because Jenkins runs
shwithset -e-like strictness.
Common Root Causes
- The command genuinely failed — compile error, test failure, lint violation, missing dependency; exit 1 is correct.
set -e/pipefailbehavior — Jenkins runsshsteps with-eby default, so the first failing command aborts the script; withoutpipefaila failure mid-pipe can be masked or exposed unexpectedly.- Expected non-zero treated as fatal — steps like
grep(no match = 1),diff,test,curl -freturn non-zero by design. - Wrong working directory or missing file — a
cdfailed earlier, so a later command exits 1. - Exit code from the last command in a multi-line step — only the final command’s status (or the first failure under
-e) determines the step result. - Tool failure vs pipeline failure — a wrapped tool exits 1 but you actually want to record the result and continue.
How to diagnose
The message itself carries no detail — scroll up. The failing command’s own output is what matters. Make the shell tell you exactly what failed by enabling tracing and strict pipe handling:
steps {
sh '''
set -euxo pipefail # -x prints each command; -o pipefail catches mid-pipe failures
npm ci
npm run build
'''
}
To capture the exact exit code without failing the step, use returnStatus:
script {
def code = sh(script: 'run-tests.sh', returnStatus: true)
echo "run-tests.sh exited ${code}"
if (code != 0) { /* decide what to do */ }
}
Reproduce on the agent to isolate tool vs pipeline. From the agent’s workspace shell (or via Manage Jenkins → Nodes → agent-01 → logs):
# On agent-01, in the job workspace
cd /home/jenkins/workspace/my-job
set -euo pipefail
npm run build; echo "exit=$?"
Check the build log around the failure and, for infra-level clues, the agent log via journalctl on the agent host:
journalctl -u jenkins-agent --since '15 min ago' | tail -50
Fixes
1. Fix the underlying command
Most of the time exit 1 is real. Read the error above the message and fix it — a failing test, a compile error, a missing package. Don’t mask a legitimate failure.
2. Make failures explicit and traceable
Add strict mode so the failing command is obvious and mid-pipe failures aren’t hidden:
steps {
sh 'set -euo pipefail; ./build.sh | tee build.log'
}
Without pipefail, ./build.sh | tee reports tee’s exit code (0) and hides a build failure — a classic false green.
3. Handle expected non-zero exit codes
For commands where non-zero is meaningful, capture the status instead of letting -e abort:
script {
// grep exits 1 when there is no match — that's not a build failure here
def hits = sh(script: 'grep -c ERROR app.log', returnStatus: true)
if (hits == 0) {
echo 'No ERROR lines found'
}
}
Or grab output with returnStdout:
script {
def sha = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
echo "Building ${sha}"
}
4. Mark the build UNSTABLE instead of FAILED
When a tool failure should flag but not hard-fail the pipeline, use catchError with a stage/build result:
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
sh 'npm run lint' // lint problems -> UNSTABLE, pipeline continues
}
}
For a softer variant that only marks unstable:
steps {
warnError('Integration tests had failures') {
sh 'npm run test:integration'
}
}
5. Distinguish tool failure from pipeline failure
Record real results (JUnit, artifacts) even when the command fails, so you keep evidence:
steps {
sh(script: 'pytest --junitxml=report.xml', returnStatus: true)
}
post {
always { junit 'report.xml' } // publish results regardless of exit code
}
Let the test report — not the raw shell exit — drive the build status.
What to watch out for
- Always add
set -euo pipefailat the top of multi-lineshsteps; the default-ealone still hides failures inside pipes. returnStatus: trueandreturnStdout: trueare mutually exclusive on a singleshcall.- Don’t wrap everything in
catchError— masking real failures produces green builds that ship broken code. - On Windows agents,
batreports%ERRORLEVEL%; the equivalent strictness differs from POSIXsh— usepowershellwith$ErrorActionPreference='Stop'for reliable failure propagation. - A
cdinto a missing directory fails the whole step under-e; usedir('subproject') { sh '...' }for clarity. - Publish test/lint reports in
post { always { ... } }so failures are diagnosable, not just red.
Related
- Jenkins Error: ‘No such DSL method found among steps’ — when the step you switched to (e.g.
warnError,withAWS) isn’t installed. - Jenkins Error: ‘GitException: command git … failed’ — a specific non-zero exit from a git subcommand inside a step.
- GitLab CI Error: ‘job failed exit code 1’ — the same exit-code-1 pattern on GitLab runners, for cross-CI teams.
More Jenkins prompts & error guides
Every Jenkins AI prompt and troubleshooting guide, in one place.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.