Skip to content

perf(lanes): configure work-unblock's stranded re-fire backoff per cause and shorten it for transient causes - #12644

Open
melodic-automation-lanes[bot] wants to merge 1 commit into
mainfrom
work-resolve-issue/issue-12635-41571640418-1
Open

melodic-automation-lanes[bot] wants to merge 1 commit into
mainfrom
work-resolve-issue/issue-12635-41571640418-1

Conversation

@melodic-automation-lanes

Copy link
Copy Markdown
Contributor

Refs: #12635

Summary

Opened by the work-resolve-issue lane. The run's own summary:

work-unblock's stranded re-fire backoff used a hard-coded 3-hour base. It now reads a per-cause base from the new limits.stranded-backoff-hours (no-run, no-report, failed-again, failed-mint, kill-switch). Each cause defaults to 3, so the behavior is unchanged unless set, and docs/conventions/pr-pipeline.yaml sets the transient causes to 1. Choice of means: a stranded run has no lane-record failure class (it never reported), so the classes are the causes the sweep already tells apart. The transient ones are those where no model ran and nothing in the issue failed (no run started, no lane token, a kill-switch refusal). no-report and failed-again keep 3 because they may fail the same way again and spend run-hours each time. turn-cap-restarts is untouched.

The run went past its turn budget (turns=85 max-turns=75): the work may be too big for one run.

Remaining work

  • Measure the median idle time from a transient stranded failure (no-run, failed-mint, kill-switch) to its re-fire for 7 days before and 7 days after this change merges, and post both on #12635.

Not verified

  • The 7-day before-and-after measurement of the median idle time from a transient failure to its re-fire is not done. It needs 7 days of lane runs after this merges and is listed in remaining.
  • node --test for label-unblocked.test.mjs and resolve.test.mjs was not run: this lane's Bash runs only the named wrappers. Left to the PR's CI.
  • .github/workflows/work-unblock.yml:51's header comment still says the backoff is 'three hours, doubling each time, at most 24'. That path is protected from lane writes, so a person should point that comment at limits.stranded-backoff-hours. It is only a comment; no behavior depends on it.
  • tidy skipped: the change touches the shared paths .github/ and docs/conventions/, which the tidy cutoffs exclude
  • build/lint/class audits beyond check-affected were not run (no build step for these .mjs files; lint and the schema checks are CI only per check-affected). Left to the PR's CI.
  • The lifecycle baseline 2026-11-01 that defines lever 3 was not found in the repository or by web search. The choice of transient causes rests on the code, not on that document.

Fix

  • .github/scripts/work-unblock/label-unblocked.mjs: removed the STRANDED_HOURS constant. The stranded threshold and backoffDue now take each cause's hours from limits.stranded-backoff-hours: no-run, no-report and failed-again via stranded(), failed-mint via backoff(), and kill-switch on the breaker ba
  • .github/scripts/work-unblock/label-unblocked.test.mjs: new test. A no-run base of 1 re-fires after 1 hour and doubles to 2 after one re-fire. The default still waits 3 hours, and no-report keeps 3 hours under the same config.
  • .github/scripts/work-resolve-issue/limits.mjs: added STRANDED_BACKOFF_DEFAULTS (3 each), STRANDED_BACKOFF_MAX (24) and resolveStrandedBackoff. readLimits now returns stranded-backoff-hours.
  • .github/actions/resolve-config/resolve.mjs: limitsOf emits stranded-backoff-hours, with the defaults overlaid by the set values.
  • .github/actions/resolve-config/resolve.test.mjs: added stranded-backoff-hours to the expected default LIMITS. New test: an override keeps the other causes, and an unknown cause or a value of 0, 25 or 1.5 is rejected.
  • docs/conventions/pr-pipeline/pr-pipeline.schema.json: added limits.stranded-backoff-hours, an object (additionalProperties false) with five integer causes, each 1-24 and default 3, and a description of where each cause's doubling starts.
  • docs/conventions/pr-pipeline/README.md: added a stranded-backoff-hours row to the limits table.
  • docs/conventions/pr-pipeline.yaml: set limits.stranded-backoff-hours (no-run 1, no-report 3, failed-again 3, failed-mint 1, kill-switch 1) with the reason. The work-unblock activity comment now points to the key instead of 'three hours'.
  • .changes/pr-pipeline/work-resolve-issue-issue-12635-41571640418-1-8cca56d5.md: new minor changelog fragment for the key.

Verification

The runner ran each check from the base copy against 73ec8a6:

  • scripts/check-changelog-fragments.sh --check: passed
  • scripts/check-changed-skills.sh --merge-base: passed

The run reports:

  • check-changelog-fragments: all 18 fragments valid
  • check-changed-skills: no changed skills, nothing to gate
  • check-affected: first run failed on a missing .changes/pr-pipeline fragment; I added one, and the re-run (also after the review fixes) gave 10 passed, 5 skipped over 9 changed files
  • fresh-context subagent review of the whole diff: no correctness bugs. It traced every assertion of the new test against the code and found each holds. It found that no other test deep-equals the resolved limits object, and that the schema validates before limitsOf spreads the set values. Its doc fin
  • read https: //github.com/melodic-software/claude-code-plugins/issues /12284: comment 6582114885 says 'The baseline's retry-tail lever, a shorter stranded re-fire backoff for transient classes, still applies' (lifecycle baseline 2026-11-01, lever 3), with work-resolve-issue at 694 run-hours and 25% f
  • The lane run: https://github.com/melodic-software/claude-code-plugins/actions/runs/41571640418

Left out: the items under Not verified.

Related

#12635

Lane-Activity: work-resolve-issue/resolve-issue
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

claude-review reviewed 73ec8a6889b1b2769f95c8d9ba4eab572c490fcf: read 9 of 9 changed file(s); 0 finding(s), 0 as inline comments.

Code review: PR #12644, a per-cause stranded re-fire backoff

Result: no blocking or advisory findings. I read every hunk of all 9 changed files. I also read the touched code at head: limits.mjs in full, and refireRefusal in label-unblocked.mjs (L1640–1764).

What I checked

  • How the setting reaches the code. Both readLimits and refireRefusal run the value through resolveStrandedBackoff, which falls back to the default of 3 for each cause. So callers that pass LIMIT_DEFAULTS or no resolved config get the old behaviour (limits.mjs#L43, label-unblocked.mjs#L1654). The other readers of readLimits (report-outcome.mjs, apply-verdict.mjs) only receive one extra key that they ignore.
  • Which hours each cause uses. Each cause reads its own value:
    • no-run, no-report and failed-again go through stranded(), which uses the value both as the first-wait threshold and as the backoff base.
    • failed-mint uses its value in backoff() at L1750.
    • kill-switch uses its value in the breaker trip's backoffDue at L1732. That branch is reached only when over >= 0, which happens only for breaker stalls, so the key's name matches what it governs.
  • Comment text. With the default of 3, hoursText(3) still renders "3 hours", so existing records and the tests that check exact text (NO_RUN_CAUSE) don't change. A value of 1 renders "1 hour".
  • The new test (label-unblocked.test.mjs L4245). I checked its timings by hand against backoff(): the re-add happens at 08:00:01 and counts as 1 re-fire, so the next re-fire is due at 10:00:01. The test expects no write at 10:00:00 and an infra comment POST at 10:00:02, which matches. This is my reading of the code only. I did not run node --test; the author also did not run it and leaves it to CI.
  • Schema, resolve-config and docs agree. The schema rejects unknown causes (additionalProperties: false) and values outside 1–24 or non-integers. limitsOf lays any set values over the defaults. The README row and the changelog fragment describe the same defaults and the same 24-hour cap. A minor bump fits a new optional config key.

Notes (not counted as findings)

  • Stale comment the lane could not edit. The author reports that the header comment in .github/workflows/work-unblock.yml (around L51) still says "three hours, doubling each time, at most 24". That file is protected from lane writes, so a person should point the comment at limits.stranded-backoff-hours. No behaviour depends on it.
  • Closing issue. The PR only refers to perf(lanes): shorten work-resolve-issue's stranded re-fire backoff for transient failure classes #12635 (Refs:) and closes no issue, so I had no acceptance criteria to check against. The author's "Remaining work" item, a 7-day before/after measurement of idle time, is not done and is left for after merge.
  • Usage, not a cost hold. pr-pipeline.yaml lowers the base for no-run, failed-mint and kill-switch from 3 h to 1 h. That means stranded issues are re-fired more often, which spends prepaid lane runs and subscription usage, not billed resources.
  • Author-claimed checks. The fragment and changed-skills checks and the check-affected run are the author's claims. I did not verify them.

Prior findings: none (round 0 of 3; the cap does not apply).

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 73ec8a6889b1b2769f95c8d9ba4eab572c490fcf: read 9 of 9 changed file(s); 0 finding(s), 0 as inline comments.

Security review: PR #12644 (head 73ec8a6)

No security issues found.

I read every hunk of all 9 changed files. Three of them are on trusted paths: .github/actions/resolve-config/resolve.mjs, .github/scripts/work-resolve-issue/limits.mjs and docs/conventions/pr-pipeline.yaml. For each one I read the whole change and checked what it controls.

Trust controls checked:

  • Broker signer (resolve.mjs, pr-pipeline.yaml): the only change is that limitsOf now outputs a stranded-backoff-hours object: defaults with any values set in config laid over them (resolve.mjs#L980-L987). The broker passes it through on its result. It changes no token scope or permissions, no lane or activity grant, and no session allowlist, and the signer's load path gets no new import. The YAML edits touch only a limits leaf and a comment on the work-unblock activity. Lane slots and activity effects are unchanged. Nothing is widened.
  • Kill switch: the backoff for a kill-switch cause now starts at 1 hour instead of 3 (label-unblocked.mjs#L1732). That only changes how soon a refused issue is re-labelled. The broker still refuses every run while the switch is on, and this PR doesn't touch check-kill-switch/. The switch can still stop a lane.
  • Merge holds: limits.mjs is imported by report-outcome.mjs and apply-verdict.mjs. readLimits now returns one more key, and none of those callers read it for a hold decision. No hold is weakened.
  • Input handling: resolveStrandedBackoff (limits.mjs#L92-L98) always returns all five causes. Any value that isn't a whole number from 1 to 24 falls back to the default of 3. The schema also rejects unknown keys (additionalProperties: false) and out-of-range values. Every stranded(...) call site passes a fixed cause (no-run, no-report or failed-again), so the lookup can't come back undefined, which would skip the wait.

Instruction surface: none of these files is a standing instruction. The comment edits only re-word the backoff description.

PR context: no embedded instructions aimed at this lane.

Spend (not a finding): with a 1-hour base for no-run, failed-mint and kill-switch, stranded issues re-fire more often. That spends more lane runs and API calls from prepaid or subscription usage, still capped by limits.restarts-per-run and the 24-hour backoff ceiling.

critical-findings: 0

@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 3, 2026
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor Author

The unattended merge path armed native auto-merge at head 73ec8a6889b1b2769f95c8d9ba4eab572c490fcf.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 3, 2026
@kyle-sexton kyle-sexton added do-not-merge Hard merge gate: do not merge while applied. and removed do-not-merge Hard merge gate: do not merge while applied. labels Nov 3, 2026

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant