# Jenkins Cause and Build Trigger Cause Tracking System: A Deep Dive into `hudson.model.Cause`

> Discover Jenkins build triggers and the Cause system. Learn how Jenkins tracks build origins via CauseAction for audit trails UI and API access.

- Repository: [Jenkins/jenkins](https://github.com/jenkinsci/jenkins)
- Tags: deep-dive
- Published: 2026-06-19

---

**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`](https://github.com/jenkinsci/jenkins/blob/main/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 like `SCMTriggerCause` to 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`](https://github.com/jenkinsci/jenkins/blob/main/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`:

```java
// 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`](https://github.com/jenkinsci/jenkins/blob/main/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`](https://github.com/jenkinsci/jenkins/blob/main/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:

1. **Attachment phase** – `onAddedTo(Run build)` fires immediately when the cause joins the build, allowing implementation-specific side effects like log file association.
2. **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:

```java
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:**

```groovy
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:**

```java
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 `Cause` class** in [`hudson/model/Cause.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/model/Cause.java) defines the contract for all build origin types.
- **`CauseAction`** attaches cause instances to `Run` objects 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 `@ExportedBean` API exposure.
- **Queue integration** via `foldIntoExisting` ensures 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`](https://github.com/jenkinsci/jenkins/blob/main/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:

```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`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/model/Run.java), which retrieves the first matching cause from the associated `CauseAction`.