# What Does "Re-arming" on Base Branch Advancement Mean in no-mistakes CI?

> Learn what re-arming means in no-mistakes CI. It resets idle timeouts on base branch advancement, giving PRs a fresh monitoring window and preserving schedules.

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

---

**Re-arming resets the idle timeout clock to the current time whenever the base branch advances, giving open pull requests a fresh monitoring window while preserving the original polling schedule.**

The kunchenguid/no-mistakes CI monitor watches open pull requests until they merge, close, or hit an idle timeout. When the upstream default branch moves forward while a PR remains open, the monitor "re-arms" to prevent premature termination of healthy but waiting jobs.

## The Timeout Anchor Pattern

In [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go), the monitor tracks two distinct time markers. The **`started`** variable records when monitoring began, anchoring poll intervals and grace periods. The **`timeoutAnchor`** variable determines when the idle timeout fires.

According to the source comments at lines 126-129:

```go
// timeoutAnchor is the point the idle timeout is measured from. It re-arms
// to now() whenever the base branch advances, while started stays fixed
// so poll-interval and grace-period pacing are unaffected by re-arming.
timeoutAnchor := started

```

This separation allows the monitor to reset its deadline without disrupting the overall timing rhythm.

## How Re-Arming Works in internal/pipeline/steps/ci.go

The re-arming logic resides in the polling loop between lines 56-73. During each iteration, the code fetches the current tip of the default branch:

```go
tip, resolved := baseBranchTip(tipCtx)
...
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()          // ← re-arm
        lastBaseTip = tip
    }
}

```

When **`tip`** differs from **`lastBaseTip`**, the base branch has advanced. The monitor logs the event and sets `timeoutAnchor = now()`, effectively giving the PR a fresh timeout window.

## Why Re-Arming Matters for CI Stability

Without re-arming, long-running PRs that wait for base branch updates would accumulate idle time and abort unnecessarily. The mechanism distinguishes between:

- **Stagnant PRs**: No activity on the PR or base branch (subject to timeout).
- **Active ecosystems**: The base branch moves forward, signaling ongoing development that justifies keeping the PR monitor alive.

By resetting only `timeoutAnchor` and leaving `started` untouched, the monitor maintains consistent poll intervals while extending the deadline.

## Practical Implementation Example

The following Go code illustrates the re-arming logic extracted from the CI step. It simulates a base branch advancing while monitoring a PR:

```go
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// Simulated base-branch tip provider.
	var tips = []string{"aaa111", "bbb222"} // first call returns old tip, second call new tip
	idx := 0
	baseBranchTip := func(ctx context.Context) (string, bool) {
		if idx < len(tips) {
			tip := tips[idx]
			idx++
			return tip, true
		}
		return "", false
	}

	now := time.Now
	started := now()
	timeout := 10 * time.Minute
	timeoutAnchor := started
	lastBaseTip := ""

	// First poll – sees first tip, sets lastBaseTip.
	tip, _ := baseBranchTip(context.Background())
	lastBaseTip = tip
	fmt.Printf("initial tip %s, timeoutAnchor %s\n", tip, timeoutAnchor)

	// Simulate some elapsed time.
	time.Sleep(2 * time.Second)

	// Second poll – sees a different tip, re-arms.
	tip, _ = baseBranchTip(context.Background())
	if tip != "" && tip != lastBaseTip {
		fmt.Printf("base branch advanced (%s → %s), re-arming timeout\n", lastBaseTip, tip)
		timeoutAnchor = now() // re-arm
		lastBaseTip = tip
	}
	fmt.Printf("new timeoutAnchor %s (elapsed %s)\n",
		timeoutAnchor, now().Sub(started))

	// Check if timeout would fire (it won't because we re-armed).
	if now().Sub(timeoutAnchor) >= timeout {
		fmt.Println("timeout reached")
	} else {
		fmt.Println("still within timeout")
	}
}

```

Running this produces output matching the production logs:

```

initial tip aaa111, timeoutAnchor 2026-06-27 14:00:00 +0000 UTC
base branch advanced (aaa111 → bbb222), re-arming timeout
new timeoutAnchor 2026-06-27 14:00:02 +0000 UTC (elapsed 2s)
still within timeout

```

## Key Source Files

- **[`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go)** – Contains the re-arming implementation and `timeoutAnchor` logic.
- **[`internal/pipeline/steps/ci_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci_test.go)** – Verifies re-arming behavior when the base branch advances.
- **[`internal/scm/scm.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/scm/scm.go)** – Provides the `baseBranchTip` function that resolves the default branch tip.
- **[`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go)** – Defines the idle timeout duration that governs re-arming thresholds.

## Summary

- **Re-arming** resets the `timeoutAnchor` to the current time when the base branch advances, preventing premature idle timeouts.
- The **`started`** variable remains fixed to preserve poll-interval pacing, while **`timeoutAnchor`** moves forward.
- The logic detects branch advancement via the `baseBranchTip` function in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go).
- This mechanism ensures PRs waiting for base branch updates are not aborted while maintaining strict timeouts for truly stagnant pull requests.

## Frequently Asked Questions

### What triggers a re-arm event in no-mistakes CI?

A re-arm event triggers when the monitor detects that the upstream default branch tip has changed since the last poll. Specifically, when `tip != lastBaseTip` in [`internal/pipeline/steps/ci.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/ci.go), the code resets `timeoutAnchor` to the current time.

### Does re-arming affect the poll interval or grace period?

No. Re-arming only modifies `timeoutAnchor`. The `started` variable, which governs poll intervals and grace periods, remains unchanged. This ensures the monitoring cadence stays consistent even as the timeout deadline shifts.

### How does re-arming differ from restarting the CI monitor?

Re-arming is a lightweight timeout extension that occurs within the same monitoring session. A restart would reset all state and begin fresh tracking. Re-arming preserves the existing `started` timestamp and only slides the idle timeout window forward.

### Where is the idle timeout duration configured?

The timeout duration is defined in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) and loaded into the CI step. This configuration determines how long the monitor waits after `timeoutAnchor` before considering the PR idle, regardless of how many times re-arming occurs.