{"slug": "when-cleanup-resets-the-retry-budget", "title": "When Cleanup Resets the Retry Budget", "summary": "An engineer's experiment on Orca, a desktop tool for organizing terminals and agent work, traced a runaway remount bug to a cleanup predicate that consulted the wrong index: terminal rows and unified UI tabs live in separate indexes, so a tab could appear present to the remount path but absent to the cleanup path, erasing its recovery history and resetting the retry budget. In fixture tests, a divergent index produced 200 remounts on the base revision versus 1 on the proposed fix, while a 16-second-spaced schedule yielded 10 versus 3. The proposed patch makes both the remount eligibility check and the disposal check rely on the same terminal-row lookup rather than lowering the retry cap.", "body_md": "A retry limiter can record an attempt correctly and still lose control before the next call. An Orca recovery-module experiment exposed the reason: display cleanup erased a terminal's recovery history while that terminal remained eligible for another remount. The useful review target is the cleanup predicate.\n\nAdapted from the [complete English research article](https://joinwell52-ai.github.io/joinwell52/en/engineering/2026-09-10-recovery-budget).\n\nOrca is a desktop tool for organizing terminals and Agent work. [PR #19745](https://github.com/stablyai/orca/pull/19745) describes a Windows crash in which eight tabs reportedly remounted 8,878 times in roughly 122.4 seconds. We did not obtain the original crash bundle; those numbers are the author's incident report, not our measurements.\n\nThe public code exposes a mechanism worth testing. Terminal rows live in one index, while unified UI tabs live in another. They usually correspond but can diverge.\n\nRemounting consults the terminal-row index. The old budget-release path consulted the unified UI index instead. The same tab could consequently receive two incompatible answers: it existed for the function performing a remount, but appeared absent to the cleanup function deciding whether to erase its recovery history.\n\nEach lookup returned a definite answer. Their combination allowed recovery to discard the evidence of its own activity.\n\nWe pinned the base and proposed head revisions and loaded the complete recovery and lookup modules. The original budget calculation, instance disposal, remount and generation-update logic remained intact. We supplied fixtures for store plumbing, time, timers, PTY and logging. We did not start Electron or reproduce graphics-memory exhaustion.\n\nEach cycle registered an instance, requested recovery and unregistered it. We then made the UI index miss a tab whose terminal row remained present. Two request schedules tested different constraints: 10 milliseconds between calls for the cooldown, and 16 seconds to move past the cooldown while remaining inside the cumulative budget window.\n\n| Scenario | Requests | Base remounts | Proposed-head remounts | \n|---|---|---|---|\n| Both indexes agree; 10ms spacing | 200 | 1 | 1 | \n| Terminal row present, UI entry absent; 10ms spacing | 200 | 200 | 1 | \n| Same divergence; 16s spacing | 10 | 10 | 3 | \n\nBoth rounds agreed. The first row shows that the old limiter was not universally ineffective. It worked when disposal could still see the tab. The second demonstrates history being reset under index divergence. The third separates the cumulative cap from the cooldown: even with requests spaced apart, the old code never retained enough history to reach its limit.\n\n*These counts describe successful remounts in the fixture's in-memory store, not production crashes.*\n\nThe proposed fix does not lower the cap or introduce a second limiter. It makes the question “can this terminal still be remounted?” and the question “has this terminal gone away?” rely on the same terminal-row lookup.\n\nReplacing a display instance does not end the lifetime of the terminal object that owns the recovery budget. If that object can still produce another recovery effect, a missing entry in a different UI projection should not erase the history constraining it.\n\nThis is a bounded correction. The two indexes can still disagree elsewhere. The patch chooses the appropriate source of identity for this budget; it does not establish system-wide index consistency.\n\nMerely proving that a storm stops is insufficient: disabling every recovery would also satisfy that test. We retained three controls.\n\n| Control | Result on both revisions | \n|---|---|\n| Fixture removes the terminal row, disposes the instance, then recreates the same ID | Two requests produce two remounts | \n| Requests at 0, 16, 32, 48 and 300.001 seconds | First three accepted, fourth constrained, final request accepted: four remounts | \n| Replay the old generation after one successful recovery | Two requests produce one remount | \n\nThe close control mutates the fixture's terminal rows; it is not a real UI close action. It checks the budget-release branch. The expiry control checks that the cap does not become permanent, while the stale-generation control checks that delayed requests cannot continue acting on a replaced display instance.\n\nTogether, these controls distinguish preserving necessary history from freezing recovery altogether.\n\nThe tempting response to a recovery storm is another limit. This case suggests inspecting the existing limit's release conditions first. Who can erase its history? Does that decision rely on the object performing the action, or on a UI projection or cache that can temporarily lose sight of it?\n\nThe same question applies to retry counts, failure lockouts and spending budgets. An accurate timestamp is ineffective if the action's own cleanup path deletes it before the next admission decision.\n\nNot every budget needs permanent persistence. Its lifetime does, however, need to cover the object it constrains, and its release predicate needs to agree with the execution predicate. The disappearance of a UI instance does not necessarily end a backing object's history.\n\nThe reusable finding is simple: **a protection mechanism must both record an action correctly and preserve that record until the next decision that depends on it.** Cleanup deserves the same scrutiny as admission.\n\nTest both the admission path and every history-deletion path. Keep controls for real object disposal, window expiry and stale requests so that stopping a storm does not silently disable valid recovery.\n\n[Full English edition](https://joinwell52-ai.github.io/joinwell52/en/engineering/2026-09-10-recovery-budget) · [中文版本](https://joinwell52-ai.github.io/joinwell52/zh/engineering/2026-09-10-recovery-budget) · [Methods and saved observations](https://joinwell52-ai.github.io/joinwell52/en/research/evidence/2026-09-10-authority-scope)", "url": "https://wpnews.pro/news/when-cleanup-resets-the-retry-budget", "canonical_source": "https://dev.to/joinwell52/when-cleanup-resets-the-retry-budget-dlj", "published_at": "2026-09-10 03:31:47+00:00", "updated_at": "2026-09-10 04:20:38.119205+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools"], "entities": ["Orca", "Electron", "GitHub", "stablyai"], "alternates": {"html": "https://wpnews.pro/news/when-cleanup-resets-the-retry-budget", "markdown": "https://wpnews.pro/news/when-cleanup-resets-the-retry-budget.md", "text": "https://wpnews.pro/news/when-cleanup-resets-the-retry-budget.txt", "jsonld": "https://wpnews.pro/news/when-cleanup-resets-the-retry-budget.jsonld"}}