Jenkins post Blocks Explained: always, success, failure, unstable, and cleanup
Why post Is More Reliable Than a Final Stage
A stage named Cleanup looks tidy, but Jenkins never reaches it if Test fails first. A Declarative post block is tied to stage or Pipeline completion, so it is the correct home for result-dependent publishing, notification, and cleanup.
pipeline {
agent { label 'linux' }
stages {
stage('Test') {
steps { sh './gradlew test' }
post {
always { junit 'build/test-results/**/*.xml' }
}
}
}
post {
always { cleanWs() }
failure { echo 'Pipeline failed' }
}
}
Try these lifecycle rules in the Jenkins Warmup post-actions exercise and Leave a Clean Workspace.
---
The Conditions That Matter
| Condition | Runs when |
|---|---|
always | The stage or Pipeline finishes with any result, including abort |
success | Result is successful |
failure | Result is failed |
unstable | Result is unstable, often because tests failed or code marked it so |
unsuccessful | Result is anything other than success |
aborted | The run was aborted, timed out, or superseded |
changed | Result differs from the previous completed run |
fixed | Current run succeeds after the previous run failed or was unstable |
regression | Result is worse than the previous completed run |
cleanup | Runs after other post conditions, regardless of result |
failure and unstable are not interchangeable. A failed sh step normally makes the build fail. JUnit can make a build unstable when test cases fail while still publishing the report. Security scanners are often deliberately mapped to unstable during rollout. Notification routing should preserve that distinction.
---
Stage-Level and Pipeline-Level Responsibilities
Use stage-level post for output owned by that stage:
stage('Integration Test') {
steps {
sh './ci/integration-test.sh'
}
post {
always {
junit testResults: 'reports/integration/*.xml'
archiveArtifacts artifacts: 'reports/integration/logs/**',
allowEmptyArchive: true
}
}
}
Use top-level post for the whole run: workspace cleanup, one delivery notification, audit metadata, and build-wide status. If every stage sends a Slack message, a single failure can generate a storm.
---
A Production Pattern
post {
success {
echo "Released build $BUILD_NUMBER"
}
regression {
mail to: 'delivery-oncall@example.com',
subject: "Regression: $JOB_NAME #$BUILD_NUMBER",
body: "Inspect $BUILD_URL"
}
fixed {
echo 'The pipeline has recovered'
}
aborted {
echo 'Build aborted or timed out; no release was promoted'
}
cleanup {
cleanWs(deleteDirs: true)
}
}
Environment variables inside a single-quoted Groovy string are not automatically expanded by Groovy; many Jenkins steps accept a Groovy string, while sh delegates expansion to the shell. Be conscious of which interpreter owns each dollar sign, especially around secrets.
Keep notification code resilient. If a webhook outage throws from post, it can distract from the original failure. Wrap non-critical notification calls with an explicit policy, log their failure, and never let a “success†notification run before artifact promotion has actually succeeded.
---
Result Semantics Before post
stage('Optional Audit') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
sh './ci/advisory-audit.sh'
}
}
post {
unstable { echo 'Audit findings need review' }
}
}
catchError makes the intended result visible. Compare that with sh './audit.sh || true': Jenkins sees exit code zero and cannot distinguish “clean†from “failed but ignored.†If a quality gate is mandatory, do not catch it at all. If it is advisory, mark the stage unstable and preserve the evidence.
---
Common Mistakes
- Publishing JUnit only in
success, which hides the report exactly when tests fail. - Cleaning before archiving diagnostics.
- Assuming
failurecatches an unstable build. - Sending credentials or raw environment dumps in notification bodies.
- Using only a final Cleanup stage.
- Letting a flaky notification overwrite the signal from the real failure.
A useful ordering is: collect evidence in stage-level always; archive or notify based on final result; clean last in cleanup. Practice result policy in Contain a Non-Critical Failure and report publication in Publish Test Results.