How CI Monitor Timeout Re-arming Works in no-mistakes When the Base Branch Advances
The CI monitor resets its idle-timeout countdown by updating the timeoutAnchor to the current time whenever the base branch tip advances, allowing green pull requests that are repeatedly rebased onto an advancing base to remain monitored indefinitely while stalled ones eventually time out.
The no-mistakes repository implements a CI monitor (the CIStep) that babysits open pull requests until they merge, close, or exceed a configured idle timeout. Understanding CI monitor timeout re-arming is essential for teams managing long-running pull requests, as the mechanism determines whether rebasing onto an advancing default branch extends the monitoring window or if the step terminates due to inactivity.
Configuring the Idle Timeout Duration
The timeout value originates from Config.CITimeout, a duration parsed from the ci_timeout entry in the global configuration file. As defined in [internal/config/config.go](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go#L22-L28), this setting controls how long the monitor waits for base branch activity before terminating.
# ~/.no-mistakes/config.yaml
ci_timeout: "168h" # 7 days idle timeout (default)
To disable timeout entirely and allow the monitor to run forever, use the unlimited keyword or any non-positive value:
ci_timeout: "unlimited" # never times out
Initializing the Timeout Anchor
When the CIStep starts, it establishes two critical timestamps. According to [internal/pipeline/steps/ci.go](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go#L25-L30), the step records started := now() and initializes timeoutAnchor := started. While started remains fixed for the step's lifetime to maintain stable poll intervals, the timeoutAnchor serves as the moving reference point for idle-timeout calculations.
Detecting Base Branch Advances
The monitor detects base branch movement through the baseBranchTip resolver, which retrieves the SHA of the upstream default branch on every poll cycle (defaulting to a 30-second window). In [internal/pipeline/steps/ci.go lines 56-74](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go#L56-L74), the code compares the resolved tip against lastBaseTip to determine if the base has advanced.
Re-arming Logic and Timeout Evaluation
When the resolver returns a tip that differs from lastBaseTip, the monitor logs the advance and updates timeoutAnchor to the current time (now()). This operation effectively restarts the idle-timeout countdown. Before each poll and after the base-branch check, the monitor evaluates now().Sub(timeoutAnchor) >= timeout. If the anchor has not been refreshed for longer than the configured CITimeout, the step exits with a timeout outcome.
This design ensures that only base branch advances reset the timeout anchor. Other activities, such as CI checks re-running or status updates, do not affect the idle-timeout calculation.
Testing and Custom Implementation
The test suite in [internal/pipeline/steps/ci_test.go](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci_test.go) validates timeout behavior and re-arming logic by simulating base-branch advances with different CITimeout values. For custom implementations, you can override the poll interval and stub the baseBranchTip resolver:
// In a custom pipeline you can override the poll interval for testing:
step := &steps.CIStep{
pollIntervalOverride: 5 * time.Second,
// baseBranchTip can be stubbed in tests to simulate advances.
}
Summary
timeoutAnchormechanism: The monitor initializestimeoutAnchorat startup but moves it forward only when the base branch tip advances.- Exclusive re-arming: Only base branch SHA changes update the anchor; CI status changes or other events do not reset the idle timeout.
- Configuration: Set
ci_timeoutin~/.no-mistakes/config.yamlusing duration strings like"168h"or"unlimited"to disable timeouts. - Stable polling: The
startedtimestamp remains fixed, ensuring consistent poll intervals regardless of re-arming activity. - Termination condition: The step exits when
now().Sub(timeoutAnchor)exceedsConfig.CITimeout, unless configured to run indefinitely.
Frequently Asked Questions
What happens if the base branch stops advancing?
If the base branch tip remains unchanged, the timeoutAnchor never updates. Once the duration between the current time and the anchor exceeds Config.CITimeout, the CI monitor terminates with a timeout outcome. The pull request remains open, but the monitoring step stops babysitting it.
Can I disable timeout re-arming while keeping the timeout active?
No. According to the source code in internal/pipeline/steps/ci.go, the re-arming logic is intrinsically linked to base branch advancement detection. The only way to prevent timeout re-arming is to disable the timeout entirely by setting ci_timeout: "unlimited" in the configuration.
How does the idle timeout differ from total step runtime?
The idle timeout specifically measures time since the last base branch advance using timeoutAnchor, not total execution time. Because the started timestamp never changes, the poll interval and pacing remain stable indefinitely, while only the idle window resets when the base advances.
Where is the default poll interval configured?
The default 30-second poll window is hardcoded in the CIStep implementation within internal/pipeline/steps/ci.go. You can override this value programmatically by setting pollIntervalOverride when constructing the step instance, as demonstrated in the test suite.
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 →