Repository navigation
[AI-3436] Deliver flow results through a server restart and a stale token - #1302
Conversation
A token that differs from the rejected one is adopted only while valid; an expired one is the stored credential, so refreshing it under the lock spends the single-use WorkOS refresh token the store actually holds, never twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only an uncoded 502/503/504 is restart noise; a coded 503 is the server's own verdict. Resending is safe because results dedupe by round token and messages by message_id, and the deadline keeps the call under Codex's tool-call abort. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without one, Codex can abandon a submit still riding out a server restart. The deadline now also refuses a retry that could run past it, so a call ends inside the 300 s timeout even when attempts hang. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
PR Summary by QodoKeep flow results deliverable through restarts and expired tokens
AI Description
Diagram
High-Level Assessment
Files changed (9)
|
Code Review by Qodo
1.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f2da5c3da
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } else if (isRace(code) && ++races < MaxAttempts) { | ||
| response.Dispose(); | ||
| await delay(RetryDelay); | ||
| continue; |
There was a problem hiding this comment.
Enforce the delivery deadline for race retries
When restart failures have already consumed most of DeliveryDeadline and the recovered server then returns no_active_flow, no_open_round, or concurrent_update, this branch performs up to four additional delays and 20-second attempts without checking the deadline. For example, several slow 503 responses followed by five slow race responses can exceed both the advertised 240-second bound and Codex's 300-second tool timeout, causing the only result-delivery call to be abandoned. Apply the same elapsed-time check before race retries, or enforce one deadline across every retry path.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 6097f9b: every retry, race-coded or restart noise, now passes one deadline check (elapsed + next delay + attempt timeout ≤ DeliveryDeadline). A race retry that would not fit returns the coded error with the retry hint. A fake-time test mixes slow 503s with no_open_round and asserts the call ends inside the deadline.
A race code arriving late in a restart window could otherwise retry past the harness's tool timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new send_flow_message call mints a new message_id, so the server cannot dedupe it against an earlier send whose response was lost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The refresh path was chosen from the token read before the lock, so a peer's token stored meanwhile could be rotated for the wrong server or endpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AI-3436 (no GitHub issue; the server half is kurrent-io/kcap-server#2233)
What & why
A hosted reviewer's
submit_review_resultwas lost across a server restart in two ways. A 503 from the ingress went straight back to the reviewer as an error. A retry after a 401 then resent a stored token that differed from the rejected one but had itself expired, because forced rotation only refreshed a token equal to the rejected one. The flow-result channel now retries 502/503/504 and transport failures for up to 240 s. Forced rotation refreshes any expired stored token. Codex getstool_timeout_sec=300on that channel so it does not abandon the call first.Where to look
TokenStoreforced rotation now refreshes a different-but-expired stored token under the profile lock, so the single-use WorkOS refresh token spent is the one the store holds. The retry deadline is checked before each wait plus attempt, so a call ends within 240 s. Other vendors have no per-server tool timeout.Verification
Reproduced the incident sequence in tests: a memoized token, a peer-written expired token, then a 401. The resend now carries a freshly refreshed token, and that test fails without the fix. Submit and send ride out 503s, 504s and connection resets, keep their message id, and give up with the retry hint after the window. A coded 503 and
stale_round_tokenare not retried. A release AOT publish of the CLI shows no IL or AOT warnings.🤖 Generated with Claude Code