Chapter 13Lesson 04~135 minutes

Groovy CPS, Serialization, @NonCPS, Pipeline State, Restarts, and Common Pipeline Programming Pitfalls: Diagnostics, Failure Modes, Security, and Performance

Diagnose CPS and serialization failures by preserving the original run, identifying the transformation boundary, distinguishing state/agent/external layers, and applying the smallest repair without weakening Script Security or durability.

DiagnosticsNotSerializableExceptionCPS mismatchScript SecurityPerformanceFirst-failure evidence

Learning objectives

  • Diagnose NotSerializableException from preserved stack/error evidence and nearby suspension points.
  • Recognize and repair calls from @NonCPS/native contexts into CPS-transformed Pipeline code.
  • Interpret CPS method-mismatch warnings caused by native methods receiving CPS closures.
  • Identify controller memory/CPU pressure caused by large or complex Pipeline state.
  • Keep sandbox/approval, restart, workspace, and external side-effect boundaries explicit during troubleshooting.

1. Evidence-first diagnostic ladder

Do not immediately rerun, lower durability, disable sandboxing, or rewrite the job. Preserve the build URL/number, source SHA, console exception, stage/step, node/workspace, and any external target state first.

  1. Preserve job/build/queue IDs and first-failure evidence.
  2. Confirm Jenkins core, Java, Pipeline: Groovy, Supporting APIs, and Script Security baseline.
  3. Confirm exact Jenkinsfile/library revision and build cause.
  4. Identify the most recent suspension/asynchronous step.
  5. List variables/closures that remain in scope across that point.
  6. Classify the boundary: CPS serialization, CPS/native mismatch, agent/workspace, security approval, or external system.
  7. Apply the smallest code repair at that boundary.
  8. Run a new build while preserving the broken run for comparison.

2. Failure A — NotSerializableException

def matcher = ('artifact-2026.09.16' =~ /artifact-(.+)/)
matcher.find()
echo "value=${matcher.group(1)}"
sleep 2
echo "again=${matcher.group(1)}"

Interpretation: the matcher is retained while Pipeline reaches a suspension. The repair is not “add retry” and not “change durability.” Extract the small value before suspension, ideally inside a pure @NonCPS helper if native Groovy semantics are needed.

3. Failure B — Pipeline step called from @NonCPS

@NonCPS
def badCompile() {
  node('lab-linux') {
    sh './build.sh'
  }
}

badCompile()

This reverses the legal call direction. Jenkins may report a CPS method mismatch, for example that it expected the helper but wound up catching node. Repair by removing @NonCPS if Pipeline steps are required, or keep the native helper pure and call Pipeline steps in CPS code.

@NonCPS
String normalizedTarget(String raw) {
  return raw.trim().toLowerCase()
}

def target = normalizedTarget(params.TARGET)
node('lab-linux') {
  sh "./build-safe.sh '${target}'"
}

4. Failure C — CPS closure passed to a native method

def orderByLength(List<String> names) {
  names.toSorted { left, right -> left.length() <=> right.length() }
}

def ordered = orderByLength(['bbb', 'a', 'cccc', 'dd'])
echo "ordered=${ordered}"

Some native Groovy methods are not CPS-transformed but receive a CPS-transformed closure. The result can be incorrect and the log may contain a method-mismatch warning. A safe repair is to encapsulate the entire native operation inside @NonCPS and return a normal serializable list:

@NonCPS
List<String> orderByLength(List<String> names) {
  return names.toSorted { left, right -> left.length() <=> right.length() }
}

5. Failure D — oversized Pipeline state and controller pressure

Symptoms may include high controller heap, CPU, garbage collection, slow persistence, or unexpectedly large run-state files. A common cause is reading large files into Groovy variables, accumulating huge maps across branches, or generating thousands of tiny CPS steps.

Preserve controller metrics/thread evidence, then reduce Pipeline state: process data on agents, consolidate shell operations, archive large files, and retain compact summaries only.

6. Failure E — restart is mistaken for external atomicity

A deployment API call may have reached the target before the controller stopped. After resume, Jenkins program state can continue, but the external system may already be changed. Before repeating the operation, query the target using the immutable operation/release ID. Resume logic must distinguish “Jenkins did not record completion” from “the target did not change.”

7. Security failure — bypassing sandbox/approval to escape CPS pain

Do not disable Script Security, broadly approve signatures, or use Script Console as a Pipeline workaround. Unsandboxed Groovy can call internal Jenkins APIs and expand controller compromise impact. The safe response is to choose a supported Pipeline step, change the CPS/native boundary, or encapsulate complex behavior in a reviewed plugin/tool rather than weakening trust controls.

8. Performance evidence and causality

Symptom Likely layer Evidence Small repair
Serialization exception near sleep/input CPS state Exception + in-scope object Extract serializable value
“expected to call … wound up catching …” CPS/native mismatch Warning + call site Correct call direction / @NonCPS boundary
High controller heap Pipeline state/computation heap/GC + variable size Agent-side processing
Workspace file missing after agent loss Agent/workspace node/workspace history Artifact/stash strategy
Duplicate external action after restart External side effect target operation ID/state Idempotency/verification

9. Intentionally broken workflow and repair sequence

Use a dedicated build with the matcher-across-sleep example. Capture the original build URL, source SHA, exception, and program.dat metadata if present. Commit a repair that returns the extracted string from a pure @NonCPS helper. Run a new build and compare. Do not delete the broken build or overwrite its evidence.

10. Troubleshooting shortcuts to reject

  • Do not lower durability just to avoid a serialization exception.
  • Do not disable the Groovy sandbox or bulk-approve signatures to make a script run.
  • Do not move untrusted builds onto the controller.
  • Do not edit program.dat or internal build XML as a repair.
  • Do not blindly rerun an external mutation after restart.
  • Do not keep giant report/object graphs in Pipeline variables.
Next lesson

Checkpoint Lab — Groovy CPS, Serialization, @NonCPS, Pipeline State, Restarts, and Common Pipeline Programming Pitfalls

Diagnose and repair three minimal CPS pitfalls, then restart the controller during a safe suspension and document exactly what Pipeline and external state resumes.

Knowledge check

What is the first action after a NotSerializableException?

What does a warning like “expected to call X but wound up catching node” suggest?

How should you respond to large controller-side Pipeline state?

Why is lowering durability not a valid serialization repair?

What must be checked before repeating a side effect after restart?

Official references and version notes

Version and compatibility note

Rechecked on 2026-09-16. Examples assume Jenkins 2.568.3 LTS (tested with Java 21 and 25), Pipeline: Groovy 4380.v6eb_8378b_9647, Pipeline: Supporting APIs 1015.v785e5a_b_b_8b_22, and Script Security 1422.v06869826dd9b_. The mandatory path is local/disposable, uses synthetic state and fake identities, and requires no commercial service. Plugin releases are independent of Jenkins core, so record the versions actually installed on your controller before applying these lessons.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.