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

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.

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).

Timeout Semantics in the CI Step

The initialization logic in internal/pipeline/steps/ci.go (lines 101-108) determines the effective timeout behavior:

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 (lines 126-134 and 150-174) implements this as follows:

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:


# ~/.no-mistakes/config.yaml

ci_timeout: "48h"

The parseCITimeout function in 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:

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 and confirms that the idle timer has reset.

Runtime Override via Environment Variable

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

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, 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 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, 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 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) 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →