# How the CI Monitor Timeout and Re-Arm Mechanism Works in no-mistakes

> Understand the CI monitor timeout and re-arm mechanism in no-mistakes. Learn how idle timeouts and base branch advancements keep long-running PRs monitored while abandoning stagnant ones.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-06-25

---

**The CI monitor in no-mistakes uses a configurable idle timeout that automatically resets (re-arms) whenever the base branch advances, ensuring long-running pull requests stay monitored while abandoning stagnant ones after 7 days by default.**

The `no-mistakes` repository provides a CI pipeline with a "babysit" step that monitors open pull requests until they merge, close, or exceed an idle timeout. Understanding the **CI monitor timeout and re-arm mechanism** is essential for configuring long-running integration workflows and ensuring your automation handles both active and dormant pull requests appropriately.

## Understanding the CI Monitor Timeout and Re-Arm Mechanism

### CITimeout Configuration

The timeout duration is controlled by the `CITimeout` setting defined in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go). The system recognizes the `DefaultCITimeout` constant of **7 days** (`7 * 24 * time.Hour`) and supports special string values including "unlimited", "none", "off", or "never" to disable automatic termination. When parsing via `parseCITimeout`, any non-positive duration results in an unlimited monitor that runs until manual interruption or PR closure.

### The Re-Arm Trigger

The **re-arm mechanism** resets the idle timer whenever the default branch tip advances. This ensures that actively rebased pull requests never time out. The logic resides in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) within the `CIStep.Execute` method, where the `timeoutAnchor` variable tracks the last reset time.

## Implementation Details in CIStep.Execute

The `CIStep.Execute` function implements the polling loop and timeout management through the following flow:

**1. Load and validate the timeout** (lines 101-108):

```go
timeout := sctx.Config.CITimeout
unlimited := timeout < 0
if timeout == 0 {
    timeout = config.DefaultCITimeout
}

```

**2. Initialize time anchors**:

```go
started := now()                // Fixed start moment for logging
timeoutAnchor := started        // Mutable anchor for idle calculation

```

**3. Check timeout condition** in the main polling loop:

```go
if !unlimited && now().Sub(timeoutAnchor) >= timeout {
    return timeoutOutcome()
}

```

**4. Detect base branch movement and re-arm** (lines 165-174):

```go
tip, resolved := baseBranchTip(tipCtx)
if resolved && tip != "" {
    if lastBaseTip == "" {
        lastBaseTip = tip
    } else if tip != lastBaseTip {
        // Base branch advanced → re-arm the timeout
        timeoutAnchor = now()
        lastBaseTip = tip
    }
}

```

The helper `resolveDefaultBranchTip` (called via `baseBranchTip`) queries the remote for the latest SHA of the default branch. When the SHA changes, the monitor treats this as a re-base event and resets `timeoutAnchor` to the current time.

**5. Dynamic polling intervals**: The loop sleeps for a dynamic `pollInterval`. If the remaining time before timeout is shorter than the standard interval, the sleep duration adjusts to ensure the timeout fires precisely without overshooting.

## Configuring the CI Monitor Timeout

Customize the behavior in your global configuration file at `~/.no-mistakes/config.yaml`:

```yaml

# Standard 7-day timeout (default)

ci_timeout: "168h"

# Unlimited monitoring (never self-terminate)

ci_timeout: "unlimited"

```

The configuration comment in [`config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/config.go) clarifies the semantics:

> "CITimeout is interpreted by the CI step as the maximum time to babysit an open PR with no base-branch movement before giving up. The monitor re-arms this timer every time the base branch advances..."

## Practical Code Examples

Inspect the effective timeout at runtime:

```go
cfg, _ := config.LoadGlobal("/home/user/.no-mistakes/config.yaml")
fmt.Printf("CI timeout = %v (unlimited = %v)\n",
    cfg.CITimeout,
    cfg.CITimeout < 0)

```

Simulate a re-arm event programmatically:

```go
step := steps.CIStep{}
step.baseBranchTip = func(ctx context.Context) (string, bool) {
    // Simulate changing tip to trigger re-arm
    return fmt.Sprintf("sha-%d", time.Now().Unix()), true
}

```

Execute with custom configuration:

```bash
no-mistakes run --config ~/.no-mistakes/config.yaml

```

## Summary

- The **CI monitor timeout** defaults to 7 days but accepts "unlimited" to run indefinitely.
- The **re-arm mechanism** resets the idle timer when `baseBranchTip` detects a new SHA in the default branch.
- Implementation lives in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) (`CIStep.Execute`) and [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go).
- The `timeoutAnchor` variable tracks the last reset time, compared against `now()` each polling cycle.
- Dynamic polling intervals ensure precise timeout enforcement without overshooting the configured duration.

## Frequently Asked Questions

### What is the default CI monitor timeout in no-mistakes?

The default timeout is **7 days** (168 hours), defined as the `DefaultCITimeout` constant in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go). This value applies when no explicit `ci_timeout` is configured or when the configuration value is set to zero.

### How does the re-arm mechanism detect base branch changes?

The mechanism calls `baseBranchTip` (which invokes `resolveDefaultBranchTip`) to query the remote for the latest SHA of the default branch. When the returned SHA differs from `lastBaseTip`, the code sets `timeoutAnchor = now()` in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go), effectively resetting the idle timer.

### Can I disable the CI monitor timeout entirely?

Yes. Set `ci_timeout` to "unlimited", "none", "off", "never", or any non-positive duration in your `~/.no-mistakes/config.yaml`. This sets the internal `unlimited` flag to true, causing the monitor to skip all timeout checks and run until the PR merges, closes, or the process receives an abort signal.

### Where is the timeout logic implemented in the source code?

The timeout logic is implemented in two primary locations: [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) handles the `CITimeout` structure, parsing, and defaults, while [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) contains the `CIStep.Execute` method that manages the polling loop, `timeoutAnchor` tracking, and re-arming logic.