You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] The restart drain fence refuses new requests for the full 60s while Codex exhausts its retries in about six seconds #6643
The restart handoff drains for up to MEMORY_DRAIN_RESTART_MS (60s). The intent is to let an in-flight turn finish, but during that whole window every NEW data-plane request is refused, and Codex spends its five sampling retries in roughly six seconds. So the client gives up well before the replacement is listening, and the turn fails even though the restart itself is quick.
Measured on this machine (2026-09-26): with the 60s default, a 42-second fence failed the turn with unexpected status 503 - and that was already after the refusal stopped being a fatal capacity error (see the related issue below). A two-second cap, plus the replacement's roughly one-second bind gap, passed. In other words the fence length, not the refusal itself, is what the client runs out of.
Reproduction
ocx start --port 10100.
Start a Codex turn.
Trigger the drain-and-restart path (dashboard POST /api/system/restart, or ocx service restart) while the turn is in flight.
Watch the turn: the client retries against a listener that is refusing for the full fence, exhausts its retries, and ends with unexpected status 503 instead of continuing against the replacement.
Version
2.78.0
Operating system
macOS 27.0.1 (arm64). The fence is shared code, so this should apply on every platform.
Provider and model
Not provider-specific.
Logs or error output
Source location: src/server/management/system-restart.ts - the deadline is set at line 366 (const restartDeadlineMs = now() + MEMORY_DRAIN_RESTART_MS;) and consumed at line 393 (const remainingMs = Math.max(0, restartDeadlineMs - now());), which is then handed to drain(undefined, remainingMs).
Suggested change
Bound the restart fence rather than the drain - keep the drain free to finish an in-flight turn, but stop refusing new requests for longer than the client can retry:
A turn cut by this cap is retried by the client against the replacement that the same handoff starts, which is why two seconds is enough: the gap it has to cover is the replacement's bind, not the full drain.
This is what we run as a local patch. Happy to open a PR if you would rather review it that way.
Related: the code carried by the refusal is the other half of this handoff problem, filed separately as #6642.
Screenshots and supporting files
None.
Redacted configuration
{ "hostname": "127.0.0.1", "port": 10100 }
Checks
I searched existing issues and documentation.
I removed secrets, tokens, account details, request credentials, and personal data.
Confirmed the current source behavior at dev f924820652: manual restart begins refusing new data-plane traffic before the flush delay, and passes the remaining shared 60-second restart budget to drain/shutdown. The configured bound is confirmed; your measured Codex retry window and successful two-second local patch remain reporter evidence, not measurements I repeated.
A focused PR is welcome, but a blanket shorter drain can interrupt an already-sent turn whose execution outcome is unknown. Please cover completed versus still-active/ambiguous turns, delayed replacement readiness, cancellation/explicit stop, automatic versus manual restart, and failure recovery using synthetic clocks/handlers. Preserve the no-automatic-replay rule for uncertain upstream execution; a replacement becoming ready does not prove resending an interrupted request is safe. Document the tradeoff instead of promising that every cut turn can retry safely.
#6642 is the separate client error-code question. I have not restarted the service or changed this timeout; retain owner review of the shared lifecycle policy.
#6686 made the drain response retryable. Shortening the 60 s fence by default can interrupt long turns, so the follow-up would be an explicit opt-in grace on the restart API; held until then.
Resolved on dev by #6724 (a12bb1f820), which carries #6703. The default 60 s restart drain is unchanged. An authenticated caller can now pass {"drainGraceMs": <1..60000>} to POST /api/system/restart for a shorter grace, so a restart doesn't hold Codex's retries behind the full 60 s fence. A cut turn may already have run upstream and is not replayed automatically. Closing manually because PRs target dev.
Activity
Ingwannu commentedon Oct 6, 2026
Confirmed the current source behavior at dev
f924820652: manual restart begins refusing new data-plane traffic before the flush delay, and passes the remaining shared 60-second restart budget to drain/shutdown. The configured bound is confirmed; your measured Codex retry window and successful two-second local patch remain reporter evidence, not measurements I repeated.A focused PR is welcome, but a blanket shorter drain can interrupt an already-sent turn whose execution outcome is unknown. Please cover completed versus still-active/ambiguous turns, delayed replacement readiness, cancellation/explicit stop, automatic versus manual restart, and failure recovery using synthetic clocks/handlers. Preserve the no-automatic-replay rule for uncertain upstream execution; a replacement becoming ready does not prove resending an interrupted request is safe. Document the tradeoff instead of promising that every cut turn can retry safely.
#6642 is the separate client error-code question. I have not restarted the service or changed this timeout; retain owner review of the shared lifecycle policy.
lidge-jun commentedon Oct 7, 2026
#6686 made the drain response retryable. Shortening the 60 s fence by default can interrupt long turns, so the follow-up would be an explicit opt-in grace on the restart API; held until then.
feat(lifecycle): allow an explicit restart drain grace (carry #6703) (#…
lidge-jun commentedon Oct 8, 2026
Resolved on
devby #6724 (a12bb1f820), which carries #6703. The default 60 s restart drain is unchanged. An authenticated caller can now pass{"drainGraceMs": <1..60000>}toPOST /api/system/restartfor a shorter grace, so a restart doesn't hold Codex's retries behind the full 60 s fence. A cut turn may already have run upstream and is not replayed automatically. Closing manually because PRs targetdev.