# How the CI Monitor Timeout Works in no-mistakes: Configuration, Idle Detection, and Re-Arming

> Understand the CI monitor timeout in no-mistakes. Learn about its default 7-day idle setting, unlimited mode configuration, and automatic re-arming to prevent premature termination during active development.

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

---

**The CI monitor timeout in no-mistakes defaults to 7 days of idle time, supports an unlimited mode via sentinel values, and automatically re-arms whenever the base branch advances to prevent premature termination during active development.**

The **no-mistakes** repository implements a sophisticated CI monitoring pipeline that watches pull requests until all checks complete. The **CI monitor timeout** mechanism prevents runs from hanging indefinitely while accommodating long-running tests and extended review cycles. Unlike absolute duration limits, this system tracks idle time—resetting the clock whenever upstream activity indicates ongoing development.

## Configuration Constants and Defaults

At the core of the timeout system are two critical constants defined in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go).

**`DefaultCITimeout`** (line 31) provides a 7-day safety net when users do not specify a custom duration. **`CITimeoutUnlimited`** (line 33) acts as a sentinel value (`-1`) that signals the monitor to run indefinitely until the pull request is merged, closed, or manually aborted.

The active configuration resides in `sctx.Config.CITimeout`, which the system populates by parsing the global configuration (`~/.no-mistakes/config.yaml`) or the repository-specific configuration ([`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml)).

## Timeout Semantics in the CI Step

The initialization logic in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) (lines 101-108) determines the effective timeout behavior:

```go
timeout := sctx.Config.CITimeout          // user-provided or default
unlimited := timeout < 0                  // <0 → “unlimited”
if timeout == 0 {                         // not set → use default
    timeout = config.DefaultCITimeout
}

```

**Unlimited mode** (`unlimited == true`) disables self-termination entirely; the monitor continues polling until external state changes (merge, close, or abort). **Finite timeout mode** (`timeout > 0`) establishes an idle timer that terminates the run if no activity occurs within the specified window.

## Re-Arming Mechanism on Base Branch Movement

The CI monitor distinguishes between the fixed `started` timestamp and the mutable `timeoutAnchor`. The idle timer calculates elapsed time as `now().Sub(timeoutAnchor)`, checking this value before each poll cycle (lines 52-55 and 152-155).

When the default branch advances, the system re-arms the timeout by resetting `timeoutAnchor` to the current time. The logic in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) (lines 126-134 and 150-174) implements this as follows:

```go
if !unlimited {
    resolveWindow := defaultBaseBranchTipResolveWindow
    tipCtx, cancel := context.WithTimeout(ctx, resolveWindow)
    tip, resolved := baseBranchTip(tipCtx)
    cancel()
    if resolved && tip != "" {
        if lastBaseTip == "" {
            lastBaseTip = tip
        } else if tip != lastBaseTip {
            sctx.Log(
                fmt.Sprintf("base branch advanced (%s..%s), re‑arming CI monitor timeout",
                shortSHA(lastBaseTip), shortSHA(tip)))
            timeoutAnchor = now()
            lastBaseTip = tip
        }
    }
}

```

This re-arming ensures that active development—indicated by commits landing on the base branch—does not trigger premature timeout failures.

## Timeout Outcomes and Termination

When the idle timer expires, the `timeoutOutcome()` function (lines 36-44) returns one of three terminal states:

- **CI timeout with failures** – Terminates with failure status, preserving the list of checks that remain broken.
- **Merge-ability timeout** – Signals that the mergeability status could not be resolved within the idle window.
- **Pure monitoring timeout** – A clean exit via `ciMonitoringTimeoutOutcome()` when no failures were observed during the monitoring period.

## Practical Configuration Examples

### Setting a Custom Timeout

Configure a 48-hour idle timeout in your global configuration file:

```yaml

# ~/.no-mistakes/config.yaml

ci_timeout: "48h"

```

The `parseCITimeout` function in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) handles this duration string. Any non-positive duration or the keywords `"unlimited"`, `"none"`, `"off"`, or `"never"` resolves to `CITimeoutUnlimited`.

### Disabling the Timeout Entirely

To run until manual completion regardless of idle time:

```yaml
ci_timeout: "unlimited"

```

This configuration prevents automatic termination, requiring explicit intervention via `no-mistakes axi abort --run <id>` or repository state changes.

### Observing Re-Arming in Logs

When the base branch advances, the monitor emits a specific log entry:

```

2026-06-28T09:00:12Z base branch advanced (a1b2c3d..d4e5f6g), re‑arming CI monitor timeout

```

This message originates from the re-arming logic in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) and confirms that the idle timer has reset.

### Runtime Override via Environment Variable

For ad-hoc testing or CI pipelines requiring fast failure:

```bash
NO_MISTAKES_CI_TIMEOUT=5m no-mistakes run .

```

The environment variable overrides file-based configuration, loading via `config.Load` during initialization.

## Summary

- **Default behavior**: The CI monitor timeout defaults to 7 days (`DefaultCITimeout`) when no configuration is provided.
- **Unlimited mode**: Set `ci_timeout` to `-1`, `"unlimited"`, or equivalent keywords to disable automatic termination.
- **Re-arming logic**: The system resets the idle timer (`timeoutAnchor`) whenever the base branch tip advances, preventing timeout during active development.
- **Termination states**: Expired timeouts result in one of three outcomes—failure with check details, merge-ability timeout, or clean monitoring timeout.
- **Configuration sources**: Values are parsed from `~/.no-mistakes/config.yaml`, [`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml), or the `NO_MISTAKES_CI_TIMEOUT` environment variable.

## Frequently Asked Questions

### What is the default CI monitor timeout duration?

The default duration is **7 days** (168 hours), defined as `DefaultCITimeout` in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) at line 31. This value applies when `sctx.Config.CITimeout` is zero or unspecified.

### How do I disable the CI monitor timeout entirely?

Set `ci_timeout` to `"unlimited"`, `"none"`, `"off"`, or `"never"` in your configuration file, or use any negative duration value such as `"-1"`. According to the parsing logic in [`config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/config.go), these values resolve to `CITimeoutUnlimited`, causing the monitor to run until the pull request is merged, closed, or manually aborted.

### What triggers the re-arming of the CI monitor timeout?

The timeout re-arms when the **base branch tip advances**. During each poll cycle, the monitor resolves the default branch tip; if it differs from `lastBaseTip`, the system updates `timeoutAnchor` to the current time. This logic appears in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) lines 126-134 and ensures that ongoing development activity does not trigger idle timeouts.

### What happens when the CI monitor timeout expires?

The `timeoutOutcome()` function (lines 36-44 in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go)) evaluates the terminal state and returns one of three outcomes: a failure status with preserved check errors if failures exist, a merge-ability timeout if merge status could not be determined, or a clean monitoring timeout via `ciMonitoringTimeoutOutcome()` if no failures were observed.