How CI Monitor Timeout Re-Arms on Base Branch Advances in no-mistakes
The CI monitor in no-mistakes restarts its idle timeout countdown by updating the timeoutAnchor to the current time whenever the base branch tip advances, allowing green PRs to remain monitored indefinitely while stalling ones eventually time out.
The no-mistakes repository implements a sophisticated CI monitor (the CIStep) that babysits open pull requests until they merge, close, or hit an idle timeout. Understanding how the CI monitor timeout re-arms when the base branch advances is essential for maintaining long-running green PRs that track active development branches without premature termination.
CITimeout Configuration and Initial Anchor Setup
The timeout duration is controlled by Config.CITimeout, which parses the ci_timeout entry from the global configuration file. In internal/config/config.go (lines 22‑28), this duration is loaded and validated, with support for standard duration strings or the keyword "unlimited".
When the CIStep initializes, it establishes two critical timestamps in internal/pipeline/steps/ci.go (lines 25‑30):
started := now()
timeoutAnchor := started
The started value remains fixed for the lifetime of the step, providing a stable reference for poll intervals and grace-period pacing. The timeoutAnchor, however, is the mutable reference point that determines when the idle timeout fires.
How Base Branch Advances Trigger Timeout Re-Arming
The re-arming logic operates on a polling cycle, checking the upstream default branch for new commits that would invalidate the current merge base.
Resolving the Base Branch Tip
On every poll (default 30‑second window), the monitor calls baseBranchTip to resolve the current SHA of the upstream default branch. This function returns the latest tip along with a resolution status, allowing the monitor to detect any movement in the base branch since the last check.
Detecting Advances and Updating the Anchor
In internal/pipeline/steps/ci.go (lines 56‑74), the monitor compares the resolved tip against lastBaseTip. When the SHA differs, indicating the base branch has advanced, the code logs the advance and immediately updates the timeout reference:
timeoutAnchor = now()
This assignment effectively re-arms the timeout, restarting the idle countdown from zero. Only base branch advances trigger this update; other activities such as CI checks re-running or status updates do not affect the anchor.
The Idle Timeout Check
Before each poll and after the base-branch validation, the monitor evaluates whether the idle limit has been exceeded:
if now().Sub(timeoutAnchor) >= timeout {
// terminate with timeout outcome
}
Because the anchor moves forward only when the base branch advances, a PR that continuously rebases onto an active default branch can remain monitored indefinitely (or until the configured limit if not set to "unlimited"). Conversely, a PR whose base has stalled will eventually trigger the timeout and terminate.
Configuring the CI Timeout
You can configure the re-arming behavior through the global configuration file. To set a 7‑day idle timeout:
# ~/.no-mistakes/config.yaml
ci_timeout: "168h"
To disable the timeout entirely and allow the monitor to run forever:
ci_timeout: "unlimited"
When testing the re-arming logic, you can override the poll interval and stub the base branch resolver. In internal/pipeline/steps/ci_test.go, the test suite validates this behavior by simulating base branch advances and verifying that timeoutAnchor updates only when the tip changes:
step := &steps.CIStep{
pollIntervalOverride: 5 * time.Second,
// baseBranchTip can be stubbed to simulate advances
}
Summary
- CITimeout is parsed from
ci_timeoutininternal/config/config.go(lines 22‑28) and controls the idle duration. - The
timeoutAnchorinitializes to the step start time ininternal/pipeline/steps/ci.go(lines 25‑30) but mutates independently of the fixedstartedtimestamp. - Base branch advances trigger re-arming by updating
timeoutAnchortonow()ininternal/pipeline/steps/ci.go(lines 56‑74). - Only tip changes restart the countdown; CI check re-runs do not affect the timeout.
- The monitor polls every 30 seconds by default, checking
now().Sub(timeoutAnchor) >= timeoutto determine termination.
Frequently Asked Questions
What triggers the re-arming of the CITimeout?
The timeout re-arms exclusively when the base branch tip advances. When the SHA returned by baseBranchTip differs from the previously recorded tip, the monitor updates timeoutAnchor to the current time, effectively resetting the idle countdown to zero.
How do I configure an unlimited CI timeout?
Set ci_timeout: "unlimited" in your ~/.no-mistakes/config.yaml, or use any non‑positive duration value. This configuration prevents the idle timeout from firing regardless of how long the base branch remains static, allowing the monitor to run indefinitely.
Does the timeout re-arm when CI checks re-run?
No. The re-arming logic specifically ignores CI check activity and only responds to base branch tip changes. As implemented in internal/pipeline/steps/ci.go, the anchor update occurs only when the default branch SHA moves forward, ensuring that transient CI failures or re-runs do not extend the monitoring window.
Where is the re-arming logic implemented?
The core re-arming implementation resides in internal/pipeline/steps/ci.go (lines 56‑74), which contains the comparison between lastBaseTip and the current resolved tip, along with the timeoutAnchor update. The timeout duration configuration is defined in internal/config/config.go (lines 22‑28), and comprehensive tests validating the behavior are located in internal/pipeline/steps/ci_test.go.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →