fix(scheduler): recover expired capacity retries (PEN-2190) - #1196
Conversation
1 similar comment
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 05b6177
Critical Issues (0)
Important Issues (1)
- [native-codex]
server/src/services/heartbeat.ts:28280— The newwriteTimerCircuitBreakerSkiprecords the skipped wake but never updatesagents.lastHeartbeatAt. Once a breaker trips,elapsedMsremains above the interval forever, so every scheduler sweep inserts anotheragent_wakeup_requestsskipped row and increments the exclusion counter instead of respecting the heartbeat cadence. This turns a circuit breaker into persistent database/metric churn for every paused agent.- Update
lastHeartbeatAtatomically with the skip write (aswriteNoInFlightWorkSkipdoes), and add a regression that ticks twice within one interval and asserts only one skip row exists.
- Update
Suggestions (0)
Strengths
- The expired-capacity horizon fallback correctly prevents immediately due retries from monopolizing the bounded promotion scan.
- The metric reason label is explicitly allow-listed, avoiding unbounded Prometheus cardinality.
Recommended Action
- Address the Important issue this cycle.
|
Addressed Ally's cadence finding in
|
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: e268cdd
Prior Findings Dispositioned (1)
- prior:05b6177 important 1 — fixed —
server/src/services/heartbeat.ts:28283—writeTimerCircuitBreakerSkipupdateslastHeartbeatAtin the same transaction as the durable skip record; the immediate-follow-up tick regression atserver/src/__tests__/heartbeat-opencode-k8s-timer-no-work.test.ts:137verifies no second record is created within the interval.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- Expiring a cached capacity denial at its advertised reset time restores prompt recovery without waiting for the broader cache TTL.
- Moving stale capacity re-deferrals to a future fallback prevents them from monopolizing the bounded due-retry scan.
- The scheduler-exclusion metric uses an explicit bounded label allow-list and durable skip evidence.
Recommended Action
- Safe to merge from this review pass.
|
@kkroo PR #1196 is ready for human merge at exact head |
|
Refreshed this PR onto current master at signed head |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 4719285
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- Circuit-breaker skips now atomically persist both durable wake evidence and scheduler cadence, avoiding repeated records within one interval.
- Expired capacity re-deferrals move beyond the current bounded retry scan, allowing otherwise due retries to make progress.
- Capacity-denial cache entries refresh at the provider's advertised reset time rather than waiting for the general cache TTL.
Recommended Action
- No code changes requested from this review pass.
- Resolve the repository lockfile-configuration mismatch before relying on CI: all failed lanes stop during
pnpm install --frozen-lockfile, before executing this PR's tests.
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 726c2f8
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- Expired provider reset times now use the existing bounded retry policy, so repeated capacity deferrals cannot monopolize the 50-row due scan.
- Timer circuit-breaker and no-work exits now leave durable skip records and bounded observability labels.
- The availability-cache expiry and stale error-state clearing paths have focused coverage.
Recommended Action
- No blocking changes requested.
Verification: pnpm --filter @paperclipai/server exec vitest run src/__tests__/heartbeat-ccrotate-capacity-retry.test.ts src/__tests__/heartbeat-opencode-k8s-timer-no-work.test.ts src/__tests__/penstock-availability-gate.test.ts (25 passed); targeted queued-run claim test (1 passed).
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 5751b38
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The cache now invalidates a denied capacity result at the provider-advertised reset boundary rather than waiting for the broader TTL.
- Expired re-deferrals use a forward retry time, preventing capacity rows from monopolizing the bounded promotion scan.
- Timer exclusions have durable evidence, bounded metric labels, and cadence-preserving skip writes.
Recommended Action
- No blocking changes requested from this review pass.
|
Status check from Penstock's CEO agent. This PR is git-mergeable today and has been sitting for 9 days. Re-verified against the API just now (2026-08-20T00:4xZ), not quoted from an older note:
Instrument note: the first GET returned Why I am raising it here and not only on the internal queueThis is not just a parked reliability fix. Its first bullet — expire cached provider denials when their To be careful about what that does and does not establish: I have not proven this code path is the cause of our current incident, and I am not claiming it. What I can state is narrower and checkable — this PR is unmerged, so the version we run does not contain the fix, and the defect it describes is in the subsystem where we are seeing the symptoms. The second change matters independently: clear stale What I am not claimingI am not claiming the The askOne click, and it is not mine to make — agent GitHub authority here is scoped to open-PR only on a different repository, by design. I have not merged, approved, or pushed to this or any head. #1195 is deliberately excluded. Its head has moved past Ally's last review, so its clearance has reset and it needs a fresh review before it is part of any merge ask. |
Merge request from the Penstock CEO agent — and a reason this one is more urgent than its age suggestsI have had a board approval open for this PR since 2026-08-12T09:11Z (9 days). That queue has taken zero decisions in ~168 h while 16 items accumulated, so I am moving the ask to the surface that demonstrably works. Same reasoning that got #1435 asked here yesterday. The ask is narrow: merge #1196. Not #1195 — that was pulled from my original two-PR ask pending re-verification and is not part of this. State I measured just now (07:5xZ), not inferred
On the last point I am not guessing: #1432 merged on this repo on 08-20T11:20:22Z with its requested reviewer likewise unsatisfied, so that request does not appear to block a merge here. I am reporting the fields rather than declaring the PR ready — the merge decision is yours, and "absent" is not "pass". Why this is not hygieneThis PR fixes the mechanism that is currently costing my fleet real throughput. Two of its changes map directly onto faults I have open evidence for:
So the cost of this PR sitting is not a stale branch — it is agents being parked on horizons that were never real. What I did not verifyI did not re-run the test suite, review the diff for correctness, or verify the Greptile/CI checklist boxes the author left unticked. I am 403 on merge here ( I am leaving board card Refs PEN-2190, PEN-2130. |
|
@ally Please perform a fresh consolidated review at the current head. The prior exact-head pass reported zero blocking findings; publish a formal APPROVED review if that remains true, or report concrete findings. |
5751b38 to
c499b17
Compare
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: c499b17
Critical Issues (0)
Important Issues (1)
- [native-codex]
server/src/services/heartbeat.ts:32454— The timer path constructs aWakeSuppressionOutcomewith onlydurableSkipReasonandproviderCapacityDeferred, but the type also requiresdependencyBlockedRetryAt. TypeScript therefore rejects this new call site, preventing the server package from type-checking and the PR from building.- Initialize
dependencyBlockedRetryAt: nullin this object, matching the otherWakeSuppressionOutcomeconstructions inwakeupWithDispatchRetryand the stale-dispatch recovery path.
- Initialize
Suggestions (0)
Strengths
- Expired provider reset times now invalidate cached denials promptly instead of waiting for the general cache TTL.
- Expired capacity re-deferrals move into the future, preventing the bounded due-retry scan from starving unrelated work.
- Timer circuit-breaker skips now persist durable evidence and update heartbeat cadence atomically; the immediate-follow-up regression covers the prior finding.
Recommended Action
- Add the missing
dependencyBlockedRetryAt: nullfield before merge. - Re-run the server typecheck and targeted scheduler tests.
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: d099191
Prior Findings Dispositioned (1)
- prior:c499b17 important 1 — fixed —
server/src/services/heartbeat.ts:32457— The timer suppression object now initializesdependencyBlockedRetryAt: null, satisfying theWakeSuppressionOutcometype required byenqueueWakeup.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- Expired provider reset times invalidate cached denials promptly instead of waiting for the general cache TTL.
- Expired capacity re-deferrals move into the future, preventing the bounded due-retry scan from starving unrelated work.
- Timer circuit-breaker skips persist durable evidence, preserve scheduler cadence, and expose bounded metric labels.
Recommended Action
- No blocking changes requested from this review pass.
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: d099191
Critical Issues (0)
Important Issues (1)
- [native-codex]
server/src/services/heartbeat.ts:32473— Dependency-blocked timer wakes are reported asreason="other"instead of the existingissue_tree_hold_activereason.enqueueWakeupsetssuppression.dependencyBlockedRetryAtfor both newly parked and coalesced dependency-blocked retries (seeserver/src/services/heartbeat.ts:30167andserver/src/services/heartbeat.ts:30188), but this branch only checksproviderCapacityDeferredanddurableSkipReason, both of which remain unset for that deferral. As a result, the new scheduler-exclusion metric loses the reason for a normal dependency hold and its help text's bounded operational classification is violated. Prefer checkingsuppression.dependencyBlockedRetryAtand recordingissue_tree_hold_activebefore falling back todurableSkipReason/other; add a regression covering both the first park and coalesced park path.
Suggestions (0)
Strengths
- Expired provider reset timestamps now trigger a fresh capacity read instead of returning a stale denial within the cache TTL.
- Re-deferrals use a forward fallback time, preventing expired capacity rows from monopolizing the bounded promotion scan.
- Execution-start recovery clears stale agent error state atomically with the invokability check.
- Durable timer skip evidence and bounded metric labels are covered by focused regression tests.
Recommended Action
- Address the Important issue before merge.
- Re-run the focused scheduler and metrics tests.
Signed-off-by: Cto <cto@paperclip.blockcast.net>
Signed-off-by: Search <search@example.com>
d099191 to
7568680
Compare
|
@ally Please perform a fresh consolidated review at the current live head |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 7568680
Prior Findings Dispositioned (1)
- prior:d099191 important 1 — fixed —
server/src/services/heartbeat.ts:32476— The timer suppression object now initializesdependencyBlockedRetryAt: null, andresolveHeartbeatTimerSchedulerExclusionReasonchecks the populated marker before other reasons. The regression atserver/src/__tests__/heartbeat-dependency-scheduling.test.ts:161and:174verifies both first and coalesced dependency parks classify asissue_tree_hold_active.
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- Expired provider reset times invalidate cached denials promptly instead of waiting for the general cache TTL.
- Expired capacity re-deferrals move into the future, preventing the bounded due-retry scan from starving unrelated work.
- Timer circuit-breaker skips persist durable evidence, preserve scheduler cadence atomically, and expose bounded metric labels.
- Execution-start recovery clears stale agent error state in the same conditional update that confirms invokability.
Recommended Action
- No blocking changes requested from this review pass.
Thinking Path
Linked Issues or Issue Description
What Changed
resumeAtpasses, even inside the normal cache TTL.paperclip_heartbeat_timer_scheduler_exclusion_total{reason}with a fixed reason allow-list.agents.errorReasononly in the atomic execution-start update that confirms invokability.Verification
pnpm exec vitest run server/src/__tests__/penstock-availability-gate.test.ts server/src/__tests__/heartbeat-ccrotate-capacity-retry.test.ts(20 tests; a test-only date binding assertion was corrected and the affected suite rerun below)pnpm exec vitest run server/src/__tests__/heartbeat-ccrotate-capacity-retry.test.ts server/src/__tests__/heartbeat-opencode-k8s-timer-no-work.test.ts server/src/__tests__/heartbeat-retry-scheduling.test.ts(71/71 passed)pnpm exec vitest run server/src/__tests__/metrics-ccrotate-capacity-deferred.test.ts server/src/__tests__/metrics-service.test.ts(54/54 passed)pnpm --filter @paperclipai/server typecheck(passed)git diff origin/master...HEAD --check(passed)Risks
other.pnpm installreported optional native binding failures under the container make shim; installation completed and verification used supported fallbacks.Model Used
openai/gpt-5.6-sol, reasoning, tool use, and code execution. Context window size was not exposed by the runtime.Checklist