How to Troubleshoot Jenkins Build Failures: A Deep Dive into the Core Execution Model
Jenkins build failures are determined by the Result enum in hudson.model.Result.java, which is calculated and persisted by the Run class and can be diagnosed by inspecting console logs, build actions, and workspace artifacts programmatically or via the REST API.
Troubleshooting Jenkins build failures requires understanding how the jenkinsci/jenkins source code tracks execution state. The platform models every build as a Run object that aggregates console output, build actions, and a final Result status. By tracing how these components interact in core/src/main/java/hudson/model/Run.java and related files, you can pinpoint exactly why a build marked itself as FAILURE, UNSTABLE, or ABORTED.
The Core Execution Model: Run, AbstractBuild, and Result
At the heart of every Jenkins build is the hudson.model.Run class. This abstract base class, defined in core/src/main/java/hudson/model/Run.java, represents any single execution of a job, tracking start time, duration, and the immutable Result. For traditional Freestyle jobs, hudson.model.AbstractBuild extends Run and adds workspace management and fingerprint handling in core/src/main/java/hudson/model/AbstractBuild.java.
The outcome of any build is encoded by the hudson.model.Result enum in core/src/main/java/hudson/model/Result.java. This enum defines five possible states: SUCCESS, UNSTABLE, FAILURE, ABORTED, and NOT_BUILT. The class provides utility methods such as isWorseThan() and combine() that Jenkins uses to determine the final status when multiple stages or post-build actions contribute to the result.
How Jenkins Calculates the Final Build Status
When a build completes, the Run.doFinish(Result result) method finalizes the state. If no result is provided (e.g., the build was aborted early), Jenkins defaults to Result.ABORTED. The method then merges the new result with any existing status using Result.combine(), which follows a strict precedence order: FAILURE > UNSTABLE > ABORTED > NOT_BUILT > SUCCESS.
This means a single failed step can override a SUCCESS, but an UNSTABLE result (typically from test failures) will not mask a FAILURE. The final value is persisted to disk, and RunListener extensions are notified via RunListener.fireOnCompleted(), allowing external systems to react to the definitive status stored in core/src/main/java/hudson/model/RunListener.java.
Diagnostic Entry Points: Console Logs and Build Actions
The console log provides the primary diagnostic surface. Jenkins streams build output to a text file on the controller, accessible via Run.getLogFile() and exposed in the UI through Run.getConsole(). Plugins can filter this stream by extending hudson.console.ConsoleLogFilter (referenced in core/src/main/java/hudson/util/ConsoleLogFilter.java) to mask secrets or annotate lines.
Beyond the console, build actions offer structured failure data. Any object implementing hudson.model.Action (defined in core/src/main/java/hudson/model/Action.java) can be attached to a run to expose test results, coverage metrics, or static analysis warnings. You can enumerate these programmatically using Run.getActions() to discover why a build was marked UNSTABLE despite appearing successful in the console.
Common Failure Patterns and Root Causes
| Symptom | Typical Cause | Source Location |
|---|---|---|
BUILD FAILURE with non-zero exit code |
A shell step returned an exit code ≠ 0, translated to Result.FAILURE in Run.doFinish(). |
core/src/main/java/hudson/model/Run.java |
UNSTABLE with test failures |
The JUnitResultArchiver creates a JUnitResultAction that evaluates isFailed() and sets the result via Result.UNSTABLE. |
core/src/main/java/hudson/model/Result.java |
ABORTED after user cancellation |
The Run.doStop() method forcibly sets the result to Result.ABORTED when a user clicks the stop button. |
core/src/main/java/hudson/model/Run.java |
| Missing artifacts | The ArtifactArchiver only preserves files when Run.getResult().isBetterOrEqualTo(Result.SUCCESS), causing apparent "missing" files on failed builds. |
Artifact archiver logic in core/src/main/java/hudson/model/ hierarchy |
| Plugin stack traces | Exceptions thrown during Run.doRun() are caught, logged to the Jenkins system log, but often hidden from the build console, leaving the result as FAILURE without clear user-facing output. |
core/src/main/java/hudson/model/Run.java |
Programmatic Troubleshooting Techniques
You can diagnose failures without web UI access by querying the Jenkins model or REST API.
1. Inspect the Console Log of the Last Build
Run this Groovy script in the Script Console (Manage Jenkins > Script Console) to retrieve the full log and result of any job:
def job = Jenkins.instance.getItemByFullName('folder/example-job')
def lastRun = job.getLastBuild()
println "Result: ${lastRun.getResult()}"
println "=== Console Log ==="
lastRun.getLog().each { line -> println line }
This script invokes Run.getResult() defined in core/src/main/java/hudson/model/Run.java and reads the log file via Run.getLog().
2. Query Build Status via REST API
Use the Jenkins JSON API from any external system to check the result without parsing HTML:
JOB="example-job"
BASE="http://jenkins.example.com"
curl -s "${BASE}/job/${JOB}/lastBuild/api/json" | jq -r '.result'
The API returns the string representation of the Result enum (e.g., FAILURE, SUCCESS), directly reflecting the internal state set by Run.doFinish().
3. Handle Unstable Results in Pipeline
If your tests fail but you want the pipeline to continue, the junit step automatically marks the run UNSTABLE:
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'mvn test || true'
junit '**/target/surefire-reports/*.xml'
}
}
}
post {
unstable {
echo 'Build is unstable due to test failures.'
}
}
}
The junit step internally creates a JUnitResultAction and calls Run.setResult(Result.UNSTABLE) if any tests fail, leveraging the logic in core/src/main/java/hudson/model/Result.java.
4. Enumerate Build Actions for Debugging
When artifacts or reports seem missing, verify which actions are attached to the run:
def run = currentBuild.rawBuild
run.getActions().each { action ->
println "Action: ${action.class.name} - ${action.displayName}"
}
This accesses the Action list defined in core/src/main/java/hudson/model/Action.java and exposed through Run.getActions().
Summary
- Build outcomes are encapsulated by the
Resultenum incore/src/main/java/hudson/model/Result.java, withFAILUREoverriding all other statuses according to the precedence incombine(). - The
Runclass incore/src/main/java/hudson/model/Run.javapersists timestamps, logs, and the final result, calculated bydoFinish()and compared viaisWorseThan(). - Console logs are accessed via
Run.getLog()and filtered throughConsoleLogFilter, while build actions provide structured data viaRun.getActions(). - Programmatic diagnosis is possible through Groovy scripts accessing the Jenkins object model or via the REST API returning JSON representations of the
Result. - Common failures map to specific source behaviors: non-zero exit codes trigger
FAILURE, test archivers triggerUNSTABLE, and user cancellations invokedoStop()to setABORTED.
Frequently Asked Questions
What is the difference between FAILURE and UNSTABLE in Jenkins?
FAILURE indicates the build did not complete successfully, typically because a shell step returned a non-zero exit code or an exception was thrown during Run.doRun(). UNSTABLE means the build completed but one or more post-build actions, such as the JUnitResultArchiver, detected warnings or test failures. The Result.java enum defines FAILURE as worse than UNSTABLE, so if any step fails, the final result will be FAILURE regardless of unstable conditions.
How do I access the console log of a specific build programmatically?
You can retrieve the console log by obtaining the Run object for a build and calling getLog(), which returns a List<String> of log lines. From the Jenkins Script Console, use Jenkins.instance.getItemByFullName('job-name').getBuildByNumber(123).getLog(). For external access, use the URL /job/JOBNAME/BUILD_NUMBER/consoleText, which streams the raw log file stored at $JENKINS_HOME/jobs/JOBNAME/builds/BUILD_NUMBER/log.
Why does my pipeline show UNSTABLE even though the shell command exit code was 0?
A zero exit code only guarantees the step succeeded; subsequent post-build steps like junit, checkstyle, or findbugs scan the workspace for result files and call Run.setResult(Result.UNSTABLE) if they detect issues. Because Result.combine() preserves the worst status encountered, even a successful shell command cannot override the UNSTABLE state set by a test result publisher.
Where are build results stored on the Jenkins controller?
Each build result is persisted to an XML file named build.xml inside the build directory, typically located at $JENKINS_HOME/jobs/JOB_NAME/builds/BUILD_NUMBER/build.xml. This file contains the serialized Run object, including the Result ordinal and timestamp. The console log is stored as a plain text file named log in the same directory, allowing administrators to inspect failures directly on the filesystem when the UI is inaccessible.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →