detached 079dd65

Retry transient OpenAI Codex processing errors

commit
079dd65ac786619c9fdf2e87247446a3ff74e336
author
patriceckhart · · 2026-07-29 16:16 UTC
parent
100f6a2
signature
unsigned
refs
tag: v0.3.24
2 files · +51 -0
M packages/core/agent.go +7 -0
@@ -471,6 +471,13 @@ func (a *Agent) canRetryError(err error, attempt int) bool {
471471 "connection refused", "connection lost", "fetch failed", "upstream connect", "reset before headers",
472472 "socket hang up", "ended without", "stream ended before", "did not get a response", "timed out",
473473 "timeout", "terminated", "unexpected eof", "transport failure",
474+ // OpenAI's ChatGPT/Codex backend returns this generic message (with a
475+ // request ID) for transient server failures and explicitly says
476+ // "You can retry your request".
477+ "an error occurred while processing your request",
478+ // Explicit retry guidance emitted by provider backends (OpenAI
479+ // Responses, AWS Bedrock stream exceptions) with varying prefixes.
480+ "you can retry your request", "try your request again", "please retry your request",
474481 }
475482 for _, needle := range needles {
476483 if strings.Contains(msg, needle) {
@@ -471,6 +471,13 @@ func (a *Agent) canRetryError(err error, attempt int) bool {
471 "connection refused", "connection lost", "fetch failed", "upstream connect", "reset before headers",471 "connection refused", "connection lost", "fetch failed", "upstream connect", "reset before headers",
472 "socket hang up", "ended without", "stream ended before", "did not get a response", "timed out",472 "socket hang up", "ended without", "stream ended before", "did not get a response", "timed out",
473 "timeout", "terminated", "unexpected eof", "transport failure",473 "timeout", "terminated", "unexpected eof", "transport failure",
474+ // OpenAI's ChatGPT/Codex backend returns this generic message (with a
475+ // request ID) for transient server failures and explicitly says
476+ // "You can retry your request".
477+ "an error occurred while processing your request",
478+ // Explicit retry guidance emitted by provider backends (OpenAI
479+ // Responses, AWS Bedrock stream exceptions) with varying prefixes.
480+ "you can retry your request", "try your request again", "please retry your request",
474 }481 }
475 for _, needle := range needles {482 for _, needle := range needles {
476 if strings.Contains(msg, needle) {483 if strings.Contains(msg, needle) {
M packages/core/agent_retry_test.go +44 -0
@@ -65,6 +65,50 @@ func TestAgentRetriesOverloadedStreamError(t *testing.T) {
6565 }
6666 }
6767
68+// codexRetryFakeClient reproduces the generic OpenAI Codex backend
69+// failure ("An error occurred while processing your request. You can
70+// retry your request, ...") on the first call and succeeds afterwards.
71+type codexRetryFakeClient struct {
72+ calls int32
73+}
74+
75+func (c *codexRetryFakeClient) Name() string { return "openai-codex" }
76+
77+func (c *codexRetryFakeClient) Stream(ctx context.Context, req provider.Request) (<-chan provider.Event, error) {
78+ call := atomic.AddInt32(&c.calls, 1)
79+ out := make(chan provider.Event, 4)
80+ go func() {
81+ defer close(out)
82+ out <- provider.EventStart{Provider: "openai-codex", Model: req.Model}
83+ if call == 1 {
84+ out <- provider.EventDone{Stop: provider.StopError, Err: fmt.Errorf("codex error: An error occurred while processing your request. You can retry your request, or contact us through our help center at help.openai.com if the error persists. Please include the request ID 60c8ebbd-20bd-42e4-b756-6e844041cfc0 in your message.")}
85+ return
86+ }
87+ out <- provider.EventDone{Stop: provider.StopEnd, Message: provider.Message{
88+ Role: provider.RoleAssistant,
89+ Content: []provider.Content{provider.TextBlock{Text: "ok"}},
90+ }}
91+ }()
92+ return out, nil
93+}
94+
95+func TestAgentRetriesCodexProcessingError(t *testing.T) {
96+ client := &codexRetryFakeClient{}
97+ a := NewAgent(client, "gpt-5.6-sol", "system", Registry{})
98+ a.RetryBaseDelay = time.Millisecond
99+
100+ if err := a.Prompt(context.Background(), "hello", nil, nil); err != nil {
101+ t.Fatalf("Prompt returned %v; want retry to succeed", err)
102+ }
103+ if got := atomic.LoadInt32(&client.calls); got != 2 {
104+ t.Fatalf("Stream calls = %d; want 2", got)
105+ }
106+ msgs := a.Messages()
107+ if len(msgs) != 2 || extractText(msgs[1]) != "ok" {
108+ t.Fatalf("messages = %v; want user + ok assistant", msgs)
109+ }
110+}
111+
68112 type partialRetryFakeClient struct {
69113 calls int32
70114 }
@@ -65,6 +65,50 @@ func TestAgentRetriesOverloadedStreamError(t *testing.T) {
65 }65 }
66 }66 }
67 67
68+// codexRetryFakeClient reproduces the generic OpenAI Codex backend
69+// failure ("An error occurred while processing your request. You can
70+// retry your request, ...") on the first call and succeeds afterwards.
71+type codexRetryFakeClient struct {
72+ calls int32
73+}
74+
75+func (c *codexRetryFakeClient) Name() string { return "openai-codex" }
76+
77+func (c *codexRetryFakeClient) Stream(ctx context.Context, req provider.Request) (<-chan provider.Event, error) {
78+ call := atomic.AddInt32(&c.calls, 1)
79+ out := make(chan provider.Event, 4)
80+ go func() {
81+ defer close(out)
82+ out <- provider.EventStart{Provider: "openai-codex", Model: req.Model}
83+ if call == 1 {
84+ out <- provider.EventDone{Stop: provider.StopError, Err: fmt.Errorf("codex error: An error occurred while processing your request. You can retry your request, or contact us through our help center at help.openai.com if the error persists. Please include the request ID 60c8ebbd-20bd-42e4-b756-6e844041cfc0 in your message.")}
85+ return
86+ }
87+ out <- provider.EventDone{Stop: provider.StopEnd, Message: provider.Message{
88+ Role: provider.RoleAssistant,
89+ Content: []provider.Content{provider.TextBlock{Text: "ok"}},
90+ }}
91+ }()
92+ return out, nil
93+}
94+
95+func TestAgentRetriesCodexProcessingError(t *testing.T) {
96+ client := &codexRetryFakeClient{}
97+ a := NewAgent(client, "gpt-5.6-sol", "system", Registry{})
98+ a.RetryBaseDelay = time.Millisecond
99+
100+ if err := a.Prompt(context.Background(), "hello", nil, nil); err != nil {
101+ t.Fatalf("Prompt returned %v; want retry to succeed", err)
102+ }
103+ if got := atomic.LoadInt32(&client.calls); got != 2 {
104+ t.Fatalf("Stream calls = %d; want 2", got)
105+ }
106+ msgs := a.Messages()
107+ if len(msgs) != 2 || extractText(msgs[1]) != "ok" {
108+ t.Fatalf("messages = %v; want user + ok assistant", msgs)
109+ }
110+}
111+
68 type partialRetryFakeClient struct {112 type partialRetryFakeClient struct {
69 calls int32113 calls int32
70 }114 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close