# Understanding the CI Monitor Timeout in no-mistakes: Configuration and Behavior

> Explore the CI monitor timeout in no-mistakes. Understand its default 7-day idle behavior, automatic re-arming, and unlimited mode for effective CI management.

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

---

**The CI monitor timeout in no-mistakes defaults to 7 days of idle time, automatically re-arms when the base branch advances to prevent premature termination, and supports an unlimited mode that runs until the PR is merged, closed, or manually aborted.**

The no-mistakes repository provides automation for managing pull requests through continuous integration monitoring. Understanding the **CI monitor timeout** behavior is essential for configuring pipeline durations that accommodate long-running tests or active development cycles without interrupting active monitoring sessions.

## Configuration Constants and Default Values

The timeout behavior is governed by constants defined in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) and parsed from user configuration files.

### Default Timeout Duration

The `DefaultCITimeout` constant is set to **7 days** (168 hours) as defined in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) at line 31. This value applies when the user does not explicitly configure a `ci_timeout` value in either the global configuration (`~/.no-mistakes/config.yaml`) or the repository configuration ([`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml)).

### Unlimited Mode Sentinel

The `CITimeoutUnlimited` constant acts as a sentinel value (`-1`) defined in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) at line 33. When this value is active, the monitor bypasses all idle timeout checks and runs indefinitely until external termination conditions are met.

## How the Timeout Logic Executes

The CI step implements timeout resolution logic in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) (lines 101–108). The code evaluates the configured duration at runtime:

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

```

If `unlimited` evaluates to true, the monitor never self-terminates based on time. Instead, it only stops when the PR is merged, closed, or the user aborts the run via `no-mistakes axi abort --run <id>`. For finite timeouts (values greater than 0), the monitor tracks idle time against a moving anchor point.

## Re-arming Mechanism on Base Branch Advancement

The CI monitor employs an **idle timeout** strategy that resets whenever the upstream default branch advances. This prevents premature termination during active development cycles where the base branch receives frequent updates.

The implementation maintains two critical timestamps in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go):

- `started`: Fixed timestamp when the monitor began
- `timeoutAnchor`: The reference point for idle time calculations, updated dynamically

The re-arming logic (lines 126–134) resolves the default branch tip and compares it against the previously stored tip:

```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()                // reset idle timer
            lastBaseTip = tip
        }
    }
}

```

Before each poll cycle (lines 52–55 and 152–155), the logic checks `now().Sub(timeoutAnchor)` against the configured timeout. When the base branch tip changes, `timeoutAnchor` resets to the current time, effectively extending the allowed monitoring period. The poll interval and grace periods operate independently of this re-arming mechanism.

## Termination Outcomes When Timeout Fires

If the idle timeout elapses without re-arming, the `timeoutOutcome()` function (lines 36–44) returns one of three distinct outcomes:

- **CI timeout with failures**: The run ends with failure status, preserving the list of still-failing checks
- **Merge-ability timeout**: The PR’s mergeability status could not be resolved before the timeout expired
- **Pure monitoring timeout**: No failures were observed; the run ends cleanly via `ciMonitoringTimeoutOutcome()`

## Configuring CI Monitor Timeouts

### Setting a Custom Duration via Global Configuration

Create or edit `~/.no-mistakes/config.yaml` to specify a custom idle timeout:

```yaml

# ~/.no-mistakes/config.yaml

ci_timeout: "48h"      # 2 days idle timeout (default is 168h = 7 days)

```

The `parseCITimeout` function in [`config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/config.go) accepts standard duration strings. Any non-positive duration (including `"0"`, `"-5m"`) or the keywords `"unlimited"`, `"none"`, `"off"`, and `"never"` resolve to `CITimeoutUnlimited`.

### Enabling Unlimited Mode

To disable timeout enforcement entirely:

```yaml
ci_timeout: "unlimited"

```

In this mode, the monitor continues until the PR is merged, closed, or manually aborted.

### Environment Variable Override

For ad-hoc executions or CI pipelines requiring temporary timeout adjustments:

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

```

The environment variable overrides both global and repository configuration values.

### Observing Re-arming in Logs

When the base branch advances, the monitor emits a log entry from the re-arming logic in [`ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/ci.go):

```

2026-06-27T12:34:56Z monitoring CI for PR #42 (timeout: 48h)...
2026-06-28T09:00:12Z base branch advanced (a1b2c3d..d4e5f6g), re‑arming CI monitor timeout
2026-06-28T09:00:12Z CI checks passed – waiting for next poll…

```

## Summary

- The **default CI monitor timeout** is 7 days (`DefaultCITimeout`), configurable via `ci_timeout` in YAML or the `NO_MISTAKES_CI_TIMEOUT` environment variable
- Set `ci_timeout` to `"unlimited"` or `-1` to enable **CITimeoutUnlimited** mode, which runs until the PR is merged, closed, or aborted
- The **idle timeout** resets (re-arms) automatically when the base branch advances, tracked via `timeoutAnchor` in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go)
- Timeout expiration triggers one of three outcomes: failure with preserved check statuses, merge-ability timeout, or clean monitoring timeout
- Configuration parsing and validation occur in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go), while monitoring logic resides in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go)

## 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 applies when no `ci_timeout` value is specified in configuration files or environment variables.

### How does the re-arming mechanism work when the base branch advances?

When the CI monitor detects that the default branch tip has changed (comparing `tip` against `lastBaseTip` in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go) lines 126–134), it updates `timeoutAnchor` to the current time. This resets the idle timer to zero, effectively extending the monitoring period by the full configured duration from the moment of advancement.

### What is the difference between a finite timeout and unlimited mode?

A finite timeout (positive duration) causes the monitor to terminate after the specified idle period unless the base branch advances to re-arm the timer. **Unlimited mode** (activated by setting `ci_timeout` to `"unlimited"` or any negative value) disables the idle timer entirely; the monitor continues running until the PR is merged, closed, or manually aborted via the CLI.

### How can I override the CI timeout for a single run without changing configuration files?

Set the `NO_MISTAKES_CI_TIMEOUT` environment variable before invoking the run command. For example, `NO_MISTAKES_CI_TIMEOUT=30m no-mistakes run .` applies a 30-minute timeout for that specific execution, overriding values from both global (`~/.no-mistakes/config.yaml`) and repository-level ([`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml)) configurations.