How the CI Monitor Timeout Is Interpreted and Re‑Armed in no‑mistakes
The CI monitor timeout in no‑mistakes is interpreted from the ci_timeout configuration as either an unlimited duration, a 7‑day default, or a parsed Go duration, and is dynamically re‑armed whenever the base branch advances during polling.
The no‑mistakes repository provides a continuous integration automation tool that monitors pull requests for status checks and mergeability. Understanding how the CI monitor timeout is configured and dynamically adjusted during execution is critical for preventing premature termination on long‑running or frequently rebased PRs.
CI Monitor Timeout Configuration and Parsing
The timeout behavior originates in the global configuration file (~/.no-mistakes/config.yaml) via the ci_timeout key, which is loaded into the Config.CITimeout field. The interpretation logic resides in internal/config/config.go.
Sentinel Values and Default Duration
The parseCITimeout function (lines 49‑66) implements specific parsing rules that map user input to internal constants defined in lines 21‑31:
- Negative durations or the keywords
unlimited,none,off, andneverare mapped toCITimeoutUnlimited(time.Duration(-1)), instructing the monitor to never self‑terminate. - Zero or omitted values resolve to
DefaultCITimeout, which equals 7 days. - Positive strings (e.g.,
"48h","2h30m") are parsed as standard Gotime.Durationvalues.
// internal/config/config.go
const (
CITimeoutUnlimited = time.Duration(-1)
DefaultCITimeout = 7 * 24 * time.Hour
)
func parseCITimeout(s string) (time.Duration, error) {
switch strings.ToLower(s) {
case "unlimited", "none", "off", "never":
return CITimeoutUnlimited, nil
}
d, err := time.ParseDuration(s)
if err != nil {
return 0, err
}
if d <= 0 {
return CITimeoutUnlimited, nil
}
return d, nil
}
Re‑Arming Logic During Pipeline Execution
During a CI run, the monitor does not use a simple global timer. Instead, it employs an anchored timeout that slides forward when the upstream base branch advances.
The Timeout Anchor Pattern
In internal/pipeline/steps/ci.go (lines 88‑92), the step initializes two temporal reference points:
started: Captures the absolute start time for logging and pacing purposes.timeoutAnchor: The floating reference against which the timeout is evaluated, initially set equal tostarted.
The monitor checks expiration via time.Since(timeoutAnchor) >= ciTimeout, but only if ciTimeout is not CITimeoutUnlimited.
Base Branch Advancement Detection
The re‑arming mechanism activates in the polling loop (lines 188‑196). On each iteration, the code resolves the current base branch tip via baseBranchTip. If the resolved tip differs from lastTip, the monitor re‑arms by resetting timeoutAnchor to the current time:
// internal/pipeline/steps/ci.go (simplified)
started := time.Now()
timeoutAnchor := started
var lastTip string
for {
// Timeout check (skipped if unlimited)
if ciTimeout != CITimeoutUnlimited && time.Since(timeoutAnchor) >= ciTimeout {
reportTimeout()
return
}
tip, got := resolveBaseBranchTip(ctx, workDir, upstream, baseSHA, defaultBranch)
if got && tip != lastTip {
// Re‑arm: base branch moved forward
timeoutAnchor = time.Now()
lastTip = tip
}
// Poll CI checks, respecting overall pacing...
time.Sleep(pollInterval)
}
This design ensures that PRs actively kept up‑to‑date with the base branch via rebasing can run indefinitely, while stagnant PRs terminate after the configured idle period. The re‑arming affects only the timeout horizon; it does not alter the started timestamp or the poll‑interval pacing.
Summary
- Configure the CI monitor timeout via
ci_timeoutin~/.no‑mistakes/config.yaml. - Negative values or keywords like
unlimiteddisable timeouts via theCITimeoutUnlimitedsentinel (time.Duration(-1)). - Omitting the setting defaults to 7 days (
DefaultCITimeout). - During execution,
internal/pipeline/steps/ci.gomaintains a slidingtimeoutAnchorthat resets wheneverbaseBranchTipdetects a new base branch commit. - This re‑arming mechanism allows long‑running PRs to remain monitored as long as they are actively rebased, while preventing indefinite waits on stale branches.
Frequently Asked Questions
What happens if I set ci_timeout to unlimited?
The parseCITimeout function in internal/config/config.go maps the strings unlimited, none, off, or never—or any negative duration—to the CITimeoutUnlimited sentinel value. When the CI step in internal/pipeline/steps/ci.go detects this value, it skips the time.Since(timeoutAnchor) >= ciTimeout check entirely, allowing the monitor to run until the PR merges or the process receives an external termination signal.
Does pushing new commits to the PR branch re‑arm the timeout?
No. The re‑arming logic specifically checks for changes to the base branch tip, not the PR branch. The code compares the resolved baseBranchTip against the stored lastTip variable. Only when the upstream base branch advances does timeoutAnchor reset to time.Now(). Activity on the PR branch itself updates CI statuses but does not affect the timeout anchor.
How does the 7‑day default behave if the base branch advances daily?
The timeout window slides forward continuously. Since timeoutAnchor resets to the current time on each base branch advancement, the time.Since(timeoutAnchor) calculation never accumulates beyond one day. Consequently, the monitor will not terminate due to timeout while the base branch maintains daily activity; termination only occurs if the base branch remains static for more than 7 consecutive days.
Can I set a timeout shorter than the poll interval?
Technically yes, but practical constraints apply. The timeout evaluation occurs at the start of each loop iteration in internal/pipeline/steps/ci.go. If the timeout is shorter than the poll interval, the monitor will detect the expiration on the subsequent iteration. However, the actual termination latency includes the current sleep duration and any grace‑period handling, so sub‑minute timeouts may not terminate with millisecond precision.
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 →