Pipeline Steps, Error Handling, retry, timeout, catchError, input, waitUntil, and Resilient Flow Control: Guided Hands-On Workflow and Core Operations
Build a disposable Pipeline that safely retries a flaky read-only check, bounds polling, records a human approval, downgrades a synthetic verification failure without hiding it, and executes an idempotently guarded local side effect.
Learning objectives
- Create a synthetic resilient Pipeline with explicit evidence for each attempt and state transition.
- Use retry only around a read-only flaky operation and prove the number of attempts.
- Bound waitUntil and human input with timeouts while avoiding unnecessary agent occupancy.
- Use catchError to continue evidence collection without converting a critical failure to green.
- Guard a simulated side effect with an immutable operation ID and independently verify the resulting target state.
1. Disposable lab and evidence baseline
Reuse the local controller and lab-linux agent pattern
from earlier chapters. The lab uses only local files under a
synthetic workspace/target directory. No package registry, cloud
account, production environment, or real credential is required.
mkdir jenkins-ch12-resilience && cd jenkins-ch12-resilience
git init
git config user.name "Jenkins Chapter 12 Lab"
git config user.email "jenkins-ch12@example.invalid"
mkdir -p scripts
cat > scripts/flaky-read.sh <<'EOF'
#!/usr/bin/env sh
set -eu
mkdir -p .lab-state
count_file=.lab-state/read-attempts
count=0
[ -f "$count_file" ] && count=$(cat "$count_file")
count=$((count + 1))
printf '%s\n' "$count" > "$count_file"
printf 'read_attempt=%s\n' "$count"
[ "$count" -ge 2 ]
EOF
chmod +x scripts/flaky-read.sh
git add . && git commit -m 'ch12: synthetic resilience lab'
git rev-parse HEAD
The script intentionally fails on its first observation and succeeds on the second. It does not mutate an external business target, so retry is safe for the exercise.
2. Build the Pipeline incrementally
pipeline {
agent none
options {
timestamps()
}
stages {
stage('Observe baseline') {
agent { label 'lab-linux' }
steps {
checkout scm
sh '''
set -eu
rm -rf .lab-state evidence synthetic-target
mkdir -p evidence synthetic-target
{
printf 'job=%s\n' "$JOB_NAME"
printf 'build=%s\n' "$BUILD_NUMBER"
printf 'build_url=%s\n' "$BUILD_URL"
printf 'node=%s\n' "$NODE_NAME"
printf 'workspace=%s\n' "$WORKSPACE"
printf 'source_sha=%s\n' "$(git rev-parse HEAD)"
} > evidence/context.txt
'''
}
}
}
}
agent none ensures later waits can occur without
holding a global executor. The first stage allocates a workspace
only for checkout and evidence.
3. Retry only the synthetic read-only check
stage('Read-only retry') {
agent { label 'lab-linux' }
steps {
retry(3) {
sh './scripts/flaky-read.sh'
}
sh 'cp .lab-state/read-attempts evidence/read-attempts.txt'
}
}
Expected evidence: attempt 1 fails, attempt 2 succeeds, attempt 3 is
never needed. The final artifact must contain 2. If the
body performed publication or deployment instead, this retry
boundary would be unjustified without an idempotency contract.
4. Bound a polling wait
Create a synthetic readiness flag from a short background process on the agent, then poll for it under a finite timeout.
stage('Bounded wait') {
agent { label 'lab-linux' }
steps {
sh '(sleep 3; touch synthetic-ready.flag) >/dev/null 2>&1 &'
timeout(time: 30, unit: 'SECONDS') {
waitUntil(initialRecurrencePeriod: 500, quiet: true) {
return fileExists('synthetic-ready.flag')
}
}
sh 'printf "ready=true\n" > evidence/readiness.txt'
}
}
This demonstrates the semantics, not an ideal production integration. For a real service, event-driven callbacks are often preferable to controller-driven polling.
5. Wait for approval without holding an executor
stage('Approval') {
steps {
script {
timeout(time: 10, unit: 'MINUTES') {
env.LAB_APPROVER = input(
message: 'Proceed with local synthetic target change?',
ok: 'Proceed',
submitter: 'lab-approver',
submitterParameter: 'APPROVER'
)
}
echo "approval recorded for this disposable lab"
}
}
}
No stage agent is declared, so Jenkins does not reserve
lab-linux just to wait for a person. Do not print
secrets; the approver identity itself is audit evidence and may be
written later to the evidence packet.
6. Guard the simulated side effect
stage('Guarded synthetic side effect') {
agent { label 'lab-linux' }
steps {
sh '''
set -eu
op_id="chapter12-${BUILD_NUMBER}"
marker="synthetic-target/${op_id}.done"
if [ -f "$marker" ]; then
printf 'already_applied=%s\n' "$op_id" | tee evidence/side-effect.txt
else
printf 'applied_by_build=%s\n' "$BUILD_NUMBER" > "$marker"
printf 'applied=%s\n' "$op_id" | tee evidence/side-effect.txt
fi
sha256sum "$marker" > evidence/side-effect.sha256
'''
writeFile file: 'evidence/approver.txt', text: "${env.LAB_APPROVER}\n"
}
}
The workspace marker is only a teaching substitute for a target-side idempotency key. In production, the target system should own durable operation identity.
7. Preserve a non-critical verification failure
stage('Synthetic optional verification') {
agent { label 'lab-linux' }
steps {
catchError(
buildResult: 'UNSTABLE',
stageResult: 'UNSTABLE',
catchInterruptions: false,
message: 'Optional synthetic verifier failed'
) {
sh 'test -f deliberately-absent-report.txt'
}
sh 'printf "verification=unstable_expected\n" > evidence/verification.txt'
}
}
This stage teaches result semantics. It is explicitly optional; a release-signing or security gate should not be downgraded merely to keep the Pipeline moving.
8. Retain the evidence
stage('Retain evidence') {
agent { label 'lab-linux' }
steps {
sh 'find evidence -maxdepth 1 -type f -print | sort > evidence/manifest.txt'
archiveArtifacts artifacts: 'evidence/*,synthetic-target/*', fingerprint: true
}
}
Expected final run result is UNSTABLE because the
optional verifier was downgraded. A green result would be wrong
evidence.
9. Challenge — choose the correct resilience layer
A synthetic HTTP status query intermittently returns 503, while the
following action would create a unique release record. Where should
retry go?
Expected design: retry only the read-only status query. Before creating the release, obtain a deterministic operation ID and check target state. Never wrap the entire query-plus-create transaction in a blind retry.
10. Cleanup
Download the evidence first. Delete only Chapter 12 lab jobs/repository resources and the synthetic target directory. Preserve shared agents/plugins and unrelated Pipeline state.
Knowledge check
Why is the flaky read step safe to retry?
It only reads synthetic state and increments lab-only attempt evidence; it does not perform an irreversible external business action.
What should the final build result be after the optional verifier?
UNSTABLE, because
catchError deliberately records the stage/build
degradation rather than hiding it.
Why is approval in an agent none stage?
So the human wait does not reserve an agent executor/workspace.
What proves the retry count?
The console attempts plus archived
evidence/read-attempts.txt.
What would make a real side effect retryable?
A verified idempotency contract such as a target-side operation key or immutable release/deployment identity, plus independent target-state verification.
Official references and version notes
- Jenkins LTS changelog — current LTS and tested Java configurations.
-
Pipeline: Basic Steps
— current
retry,timeout,catchError,waitUntil,sleep, and related step contracts. -
Pipeline: Input Step reference
—
input, submitter restrictions, identifiers, and captured approver identity. - Pipeline: Basic Steps plugin — current plugin baseline and dependencies.
- Pipeline: Input Step plugin — current plugin baseline and security history.
- Jenkins Pipeline handbook — durable Pipeline execution and Jenkinsfile concepts.
- Pipeline Best Practices — controller/agent boundaries and safe Pipeline design.
Rechecked on 2026-09-16. Examples assume Jenkins 2.568.3 LTS, tested with Java 21 and 25, Pipeline aggregator 608.v67378e9d3db_1, Pipeline: Basic Steps 1098.v808b_fd7f8cf4, and Pipeline: Input Step 560.v56198a_642157. The mandatory path is local/disposable, uses synthetic state and fake identities, performs no production deployment/publication, and does not require commercial services. Always record the versions actually installed on your controller because plugins release independently from Jenkins core.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.