Skip to content

feat: pause and resume every lane on an upstream outage without a human re-arm #9205

Closed

Description

@kyle-sexton

No related issue: a new cross-lane posture the owner decided on 2026-10-22 (Q677).

Summary

During GitHub's API incidents on 2026-10-22 (bad reads 14:18 to about 17:41Z), degraded answers made pr-merge file false demotions that needed the owner to close them (#9145, #9167, #9168). The owner wants every lane to pause on an outage and resume by itself as soon as the service recovers, without hammering it. Owner decision Q677 (2026-10-22, "Yes, go with recommendations."): adopt this posture for every lane.

Fix

The posture, from the research in this session (sources: AWS Builders' Library, timeouts, retries and backoff with jitter, GitHub REST rate limits, Google SRE, handling overload):

  1. Scripts retry; the model never does. At most 3 attempts per call. Wait for retry-after if present; for a primary rate limit wait until x-ratelimit-reset; for a secondary limit at least 60 s; otherwise capped exponential backoff with full jitter. A wait beyond a small per-step budget means unknown (2), not a sleep. Non-idempotent writes are not retried.
  2. A read that fails, or a 200 whose completeness check fails, makes the verdict unknown: hold, file nothing, merge nothing, exit green with a notice.
  3. The next existing cron tick is the probe. No stored breaker, no re-arm, no status-page trigger.
  4. Demote or file a failure only on positive evidence from a complete read. A record a lane filed is closed by the lane when a later complete read contradicts it.
  5. The GitHub status page never gates, opens or closes anything; it may be quoted in a hold notice as context. On 2026-10-22 it showed only the Actions component, 14:18 to 15:18Z.
  6. Don't amplify load: serial reads, conditional requests (etag) for repeated polls, default single-pending concurrency on scheduled lanes, optional work skipped while unknown, other lanes off minute 0.

Slices, in order:

  • A shared retry helper that the lanes' scripts call, replacing pr-merge's fixed 15 s/30 s listing loop in pr-merge.yml.
  • Element 4's self-close for lane-filed records (for example a post-merge-failure record such as Post-merge dispatch failure after #9163..#9166: lane not demoted #9177 closing itself when main's next full run is green).
  • Each lane audited against elements 1-6, one slice per lane.

Open gaps from the research, to settle in the first slice: whether a degraded GitHub 200 reports the true total_count; Claude Code's own API retry policy; how idle in-step waits bill against Actions minutes.

Verification

  • Each slice names the element it implements and a test for it.
  • A degraded-read test (a listing answering 200 with items missing) holds the lane without filing or demoting, and the next complete read clears it.

Related

#9145, #9167, #9168, #9170, #9186

Activity

  1. kyle-sexton commented on Oct 22, 2026

    @kyle-sexton
    MemberAuthor

    Decision recorded. Owner Q677 (2026-10-22, "Yes, go with recommendations."): adopt this posture for every lane, starting with the shared retry helper.

  2. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 22, 2026
  3. github-actions commented on Oct 22, 2026

    @github-actions
    Contributor

    Proposed work class: work-class: structural
    Basis: automated intake triage; the lanes App applied this class.

    Size: Medium
    Complexity: Medium
    Uncertainty: Known
    Flags: none
    Blocked by: none

    Decision recorded. The owner answered in the Refinement and Issue Triage sessions (2026-10-25), deciding the open items below on the facts:

    go with whatever the facts/data/metrics + /discovery:research says and ensure we're aligned with SOLID, DRY/SSOT principles

    I should only ever be in the loop ONLY when necessary (intent/vision changed, something came up during implementation that impacts trajectory/alignment, OR a high security risk) - I've covered this all before

    [quote truncated; the full decision is at the link]

    #9205 (comment)

    This decision settles the open questions. The issue is now slice 1: a bash retry helper beside rate-stop.bash that replaces the fixed listing loop in pr-merge.yml. It holds green with a notice when the read is unknown. It comes with the retry, budget, no-write-retry and degraded-read tests, and the PR answers the three research gaps. Self-close and per-lane audits are separate follow-up issues.

    Automated triage, applied by the lanes App.

  4. added and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 22, 2026
  5. github-actions commented on Oct 26, 2026

    @github-actions
    Contributor

    The triage sweep dispatched intake triage for this issue (attempt 1 of 2).

  6. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Oct 26, 2026
  7. melodic-automation-lanes commented on Oct 26, 2026

    @melodic-automation-lanes
    Contributor

    Not admitted to the issue lane: a criterion names .github/workflows/pr-merge.yml, which the issue lane cannot push (patch-protected-path); a builder session takes it. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).

  8. melodic-automation-lanes commented on Oct 26, 2026

    @melodic-automation-lanes
    Contributor

    The latest triage verdict holds nothing: admission moved the issue to status: confirmed; its comment says why.

  9. melodic-automation-lanes commented on Oct 26, 2026

    @melodic-automation-lanes
    Contributor

    Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.

    Intended outcome: A shared retry helper that lane scripts call replaces pr-merge's fixed 15 s/30 s listing loop, as the first slice of the pause-and-resume posture.

    Related work

    Open questions

    1. The body has three slices and no task list. Should this issue be narrowed to slice 1 (the shared retry helper plus the pr-merge swap), with self-close and per-lane audits filed as separate issues?
      RECOMMENDED: Yes: keep this issue to slice 1, and file slice 2 (self-close of lane-filed records) and one audit issue per lane afterward. Basis: judgment: the triage comment says the work is too big for one change and must be split into the listed slices
    2. Where should the shared retry helper live, and in what language?
      RECOMMENDED: A bash helper beside the existing .github/scripts/pr-merge/rate-stop.bash and .github/actions/log-api-budget/api-budget.bash, since the pr-merge listing loop is bash and already sources those. Basis: .base/.github/workflows/pr-merge.yml:739
    3. The current loop never retries a rate limit and exits 1 on it (pr-merge.yml:737,753). Element 1 says to wait for retry-after or the reset when within a small per-step budget, and otherwise treat the result as unknown and exit green. What is the per-step wait budget, and should the exit change from red to green with a notice?
      RECOMMENDED: A 60 s budget per step; above it, exit 0 with a ::notice:: hold. The current exit 1 on exhausted retries and on a rate stop becomes a green hold, per element 2. Basis: .base/.github/workflows/pr-merge.yml:753-758
    4. The body leaves three gaps to settle in the first slice: whether a degraded 200 reports the true total_count, Claude Code's API retry policy, and idle-wait billing. Should the helper's acceptance wait on them?
      RECOMMENDED: No: the slice records what it finds on each in a doc or the PR, and the helper ships with a completeness check that is pluggable per caller. Basis: judgment: the triage comment says the gaps can be answered inside the first slice
    5. Which test proves slice 1?
      RECOMMENDED: A bats or pytest test that stubs gh to fail twice then succeed (3 attempts max), honors retry-after, and returns unknown without sleeping past the budget; plus a degraded-listing test where a 200 has missing items holds without filing, and a later complete read clears it. Basis: The issue's Verification section

    Sources read

    • /home/runner/work/_temp/refine-data/context.json: owner decision Q677 adopts the posture for every lane, starting with the shared retry helper; the body lists three slices and no task list
    • /home/runner/work/claude-code-plugins/claude-code-plugins/.base/.github/workflows/pr-merge.yml:735-762: the listing loop makes 3 attempts with sleep attempt*15, never retries a rate limit, and exits 1 on a rate stop or 3 failures
    • /home/runner/work/claude-code-plugins/claude-code-plugins/.base/.github/workflows/pr-merge.yml:739-741: sources api-budget.bash and rate-stop.bash, existing shared helpers
    • /home/runner/work/claude-code-plugins/claude-code-plugins/.base/.github/workflows/pr-merge.yml:332: other retry loops with their own wait logic that a shared helper would replace

    Automated refinement by the lanes App. Answer with a comment whose first line is **Decision recorded.**.

  10. kyle-sexton commented on Oct 27, 2026

    @kyle-sexton
    MemberAuthor

    Decision recorded. The owner answered in the Refinement and Issue Triage sessions (2026-10-25), deciding the open items below on the facts:

    go with whatever the facts/data/metrics + /discovery:research says and ensure we're aligned with SOLID, DRY/SSOT principles

    I should only ever be in the loop ONLY when necessary (intent/vision changed, something came up during implementation that impacts trajectory/alignment, OR a high security risk) - I've covered this all before

    The posture itself is owner decision Q677, recorded on the issue (#9205 (comment)):

    Decision recorded. Owner Q677 (2026-10-22, "Yes, go with recommendations."): adopt this posture for every lane, starting with the shared retry helper.

    The ledger row is .work/poteto-review/interview-checklist.md:722 ("one posture issue, shared retry helper first"). No owner ledger names #9205 otherwise.

    Questions from intake's refinement (#9205 (comment)):

    1. Narrow this issue to slice 1?
      Decided: Yes. This issue is slice 1: the shared retry helper and the swap in pr-merge's listing loop. Basis: Q677 says "starting with the shared retry helper", and admission holds the issue as Size large pending the split (feat: pause and resume every lane on an upstream outage without a human re-arm #9205 (comment)). The body's other two slices become new issues that Issue Triage files: (a) element 4's self-close of lane-filed records, and (b) one audit issue per lane against elements 1-6. This record files nothing.
    2. Where does the helper live, in what language?
      Decided: A bash file beside .github/scripts/pr-merge/rate-stop.bash, which the listing step already sources (.github/workflows/pr-merge.yml:745-746). The existing retry helper, retry-fetch.mjs, is a Node fetch wrapper (.github/scripts/work-resolve-issue/retry-fetch.mjs:1-16) and cannot wrap the bash gh pr list loop, so it does not cover this.
      The helper adds no header parsing of its own. The rate guard already turns retry-after, x-ratelimit-reset and the 60 s secondary-limit fallback into a recorded stop (.github/actions/log-api-budget/api-budget.bash:257, :273, :277, :164), readable with api_budget_stop_until (:185) and rate_stop_from (.github/scripts/pr-merge/rate-stop.bash). The helper waits until that stop when it fits the budget.
    3. Per-step budget, and red or green on exhaustion?
      Decided: Green with a notice, per element 2 of the body ("hold, file nothing, merge nothing, exit green with a notice"). Today the loop exits 1 on a rate stop and after 3 failures (.github/workflows/pr-merge.yml:758-765). Neither the body nor Q677 gives a budget number ("a small per-step budget"), so this record sets none: the PR states the value it passes and why.
    4. Do the three research gaps block the helper?
      Decided: No. The PR records its answer to each. Basis: the body says they are "to settle in the first slice" (feat: pause and resume every lane on an upstream outage without a human re-arm #9205).
    5. Which tests?
      Decided: A test per element the slice implements, and the degraded-read test. Basis: the body's Verification section (feat: pause and resume every lane on an upstream outage without a human re-arm #9205).

    Build: host session
    Builder: Automation Lanes

    Acceptance criteria:

    • A bash helper beside .github/scripts/pr-merge/rate-stop.bash runs a call at most 3 times. For a rate limit it waits until the rate guard's recorded stop; otherwise it waits a capped exponential backoff with full jitter. A wait past the caller's budget is not slept: the helper returns unknown. A non-idempotent write is never retried.
    • The listing loop in .github/workflows/pr-merge.yml calls the helper in place of its fixed sleep $((attempt * 15)) loop.
    • When the listing is unknown (a failed read, a wait past the budget, or a 200 that fails the completeness check), the step ends green with a ::notice:: hold, and the job lists, demotes, files and merges nothing.
    • A test stubs gh to fail twice then succeed and passes in 3 attempts; one asserts a wait past the budget returns unknown without sleeping; one asserts a write is not retried.
    • A degraded-read test: a listing that answers 200 with items missing holds the lane without filing or demoting, and the next complete read clears it.
    • The PR body states its answer to each of the body's three open gaps: whether a degraded 200 reports the true total_count, Claude Code's own API retry policy, and how idle in-step waits bill against Actions minutes.
  11. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    and removed
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 27, 2026
  12. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Oct 27, 2026
  13. kyle-sexton commented on Nov 2, 2026

    @kyle-sexton
    MemberAuthor

    Delivered: closing as completed.

    The 2026-10-27 record narrowed this issue to slice 1, and #11596 (32cf9a9) delivered it:

    • .github/scripts/pr-merge/retry-call.bash sits beside rate-stop.bash, with at most 3 runs, the rate guard's stop, full-jitter backoff and a budget that returns unknown.
    • pr-merge's listing calls it, and an unknown listing ends green with a Merge lane hold notice.
    • The retry, budget, no-write-retry and degraded-read tests are in.
    • The PR body answers the three open gaps.

    The two remaining slices are filed so their scope stays open:

    Author: the issue-triage session.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.

    Fields

    No fields configured for Feature.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions