Jenkins Cause and Build Trigger Cause Tracking System: A Deep Dive into `hudson.model.Cause`
Jenkins tracks why every build starts by attaching Cause objects to the Run via CauseAction, enabling audit trails, UI visibility, and API access to build origins.
The jenkinsci/jenkins repository implements a robust Jenkins Cause and build trigger cause tracking system that records the origin of every build execution. This mechanism centers on the hudson.model.Cause abstract class and its companion CauseAction, which together provide comprehensive auditing capabilities across the entire build lifecycle. Understanding this architecture is essential for plugin developers and automation engineers who need to trace build provenance or implement custom trigger logic.
Core Architecture: Cause and CauseAction
The Cause Abstract Class
At the foundation of the Jenkins cause tracking system lies hudson.model.Cause, an abstract class defined in core/src/main/java/hudson/model/Cause.java. This class establishes the contract that all build triggers must implement, requiring subclasses to override getShortDescription() to provide human-readable explanations of why the build started.
The Cause class also defines critical lifecycle hooks:
onAddedTo(Run build)– Invoked when the cause attaches to a build, allowing causes likeSCMTriggerCauseto persist polling logs to disk.onLoad(Run build)– Called during deserialization to re-establish transient state after Jenkins restarts.
CauseAction: Attaching Causes to Builds
The hudson.model.CauseAction class (located in core/src/main/java/hudson/model/CauseAction.java) serves as the container that attaches one or more Cause instances to a Run object. This action stores causes in a multiset (Map<Cause,Integer>), enabling Jenkins to count duplicate occurrences of the same trigger type.
When scheduling builds via scheduleBuild2(), triggers wrap their Cause instances in a CauseAction:
// Example pattern from SCMTrigger.java
SCMTriggerCause cause = new SCMTriggerCause(logFile);
Action[] queueActions = new Action[additionalActions.length + 1];
queueActions[0] = new CauseAction(cause); // Attaches cause to queue item
Built-in Cause Types in Jenkins
Jenkins ships with several concrete Cause implementations, each representing a distinct trigger source:
| Trigger Source | Class Name | Location | Creation Context |
|---|---|---|---|
| Manual user trigger | UserIdCause (or legacy UserCause) |
hudson.model.Cause |
Created when users click "Build Now" |
| Timer trigger | TimerTriggerCause |
hudson.triggers.TimerTrigger |
Instantiated by cron-scheduled triggers |
| SCM polling | SCMTriggerCause |
hudson.triggers.SCMTrigger |
Generated when polling detects repository changes |
| Upstream build | UpstreamCause |
hudson.model.Cause |
Created in Run.scheduleBuild2 when downstream jobs trigger |
| Remote API/CLI | RemoteCause |
hudson.model.Cause |
Constructed by external HTTP/CLI calls |
| Custom plugins | Any Cause subclass |
Plugin source | Defined by third-party plugin authors |
How Build Triggers Create and Attach Causes
The integration between triggers and the cause system follows a consistent pattern across Jenkins core. When SCMTrigger detects changes in core/src/main/java/hudson/triggers/SCMTrigger.java, it constructs a SCMTriggerCause and wraps it in a CauseAction before queuing the build (around line 667).
The Queue.Item class (core/src/main/java/hudson/model/Queue/Item.java) participates in this flow by holding the CauseAction. When items are re-queued, the method CauseAction.foldIntoExisting merges duplicate causes, ensuring accurate counting of why a job remains in the queue.
Lifecycle Hooks and Persistence
Causes in Jenkins implement a two-phase persistence model that maintains state across Jenkins restarts:
- Attachment phase –
onAddedTo(Run build)fires immediately when the cause joins the build, allowing implementation-specific side effects like log file association. - Deserialization phase –
onLoad(Run build)restores transient fields when Jenkins loads build records from disk.
This design ensures that SCMTriggerCause instances retain references to their polling logs even after system restarts, while UpstreamCause objects maintain their links to parent build numbers.
Implementing Custom Causes in Plugins
Plugin developers can extend the Jenkins Cause and build trigger cause tracking system by subclassing Cause and overriding required methods:
public final class MyPluginCause extends Cause {
private final String detail;
public MyPluginCause(String detail) {
this.detail = detail;
}
@Override
public String getShortDescription() {
return "Triggered by MyPlugin: " + detail;
}
}
// Scheduling a build with the custom cause:
Job<?,?> downstream = ...;
Cause custom = new MyPluginCause("special flag");
downstream.scheduleBuild2(0, new CauseAction(custom));
The @ExportedBean annotation on Cause ensures that custom implementations automatically expose their data via Jenkins' remote API at endpoints like /api/json.
Querying Causes Programmatically
Retrieve cause information from running builds using Groovy scripts or Java APIs:
Reading a specific cause type via Groovy console:
def build = Jenkins.instance.getItemByFullName('my-job').getLastBuild()
def cause = build.getCause(hudson.model.Cause$UserIdCause)
println cause?.getShortDescription() // Output: "Started by user alice"
Listing all causes associated with a build:
Run<?,?> run = ...;
CauseAction ca = run.getAction(CauseAction.class);
if (ca != null) {
for (Cause c : ca.getCauses()) {
System.out.println(c.getShortDescription());
}
}
The getCauses() method returns the multiset of causes, preserving the count of identical trigger occurrences.
Summary
The Jenkins Cause and build trigger cause tracking system provides complete provenance tracking through these key mechanisms:
- Abstract
Causeclass inhudson/model/Cause.javadefines the contract for all build origin types. CauseActionattaches cause instances toRunobjects using a multiset to count duplicates.- Lifecycle hooks (
onAddedTo,onLoad) enable persistence and resource management for trigger-specific data. - Built-in implementations cover user actions, timers, SCM changes, upstream builds, and remote triggers.
- Plugin extensibility allows custom causes via subclassing and
@ExportedBeanAPI exposure. - Queue integration via
foldIntoExistingensures accurate cause counting during build scheduling.
Frequently Asked Questions
How does Jenkins display the "Started by user X" message on build pages?
Jenkins renders this text by calling getShortDescription() on the UserIdCause attached to the build. The CauseAction retrieves all causes from the Run object, and the UI iterates through them to display human-readable explanations of why the build triggered.
Can a single build have multiple causes?
Yes. The CauseAction stores causes in a Map<Cause,Integer> multiset, allowing multiple causes of the same or different types. This occurs when several triggers coincide—for example, when a user manually starts a build that was also scheduled by a timer, or when foldIntoExisting merges causes during queue re-processing.
What is the difference between UserCause and UserIdCause?
UserCause is the legacy implementation that identifies users by display name, while UserIdCause (the modern replacement) uses persistent user IDs. Both extend Cause and exist in core/src/main/java/hudson/model/Cause.java, but UserIdCause provides more reliable user tracking across Jenkins reconfigurations.
How do I access build causes from a pipeline script?
Access the current build's causes through the currentBuild.rawBuild property in Pipeline Groovy:
def causes = currentBuild.rawBuild.getCause(hudson.model.Cause.class)
causes.each { cause ->
echo "Cause: ${cause.getShortDescription()}"
}
This delegates to Run.getCause() in core/src/main/java/hudson/model/Run.java, which retrieves the first matching cause from the associated CauseAction.
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 →