Skip to content

fix(auth): treat empty upstream completions as retriable failures - #4881

Open
warelik wants to merge 10 commits into
router-for-me:devfrom
warelik:fix/empty-completion-retry
Open

fix(auth): treat empty upstream completions as retriable failures#4881
warelik wants to merge 10 commits into
router-for-me:devfrom
warelik:fix/empty-completion-retry

Conversation

@warelik

@warelik warelik commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Providers occasionally return HTTP 200 with a semantically empty completion — finish_reason: stop, zero content, zero tool calls, zero completion tokens (observed on Gemini AI Studio-path providers with long tool-call conversations). Today this counts as success: the client gets a useless empty reply, no retry happens, and the auth is never cooled down.

This change treats a genuinely empty terminal completion as a retriable failure:

  • Detection (empty_completion.go): conservative predicates for OpenAI-style SSE, Claude, Gemini, and Interactions. Empty iff terminal AND zero non-whitespace content AND zero tool calls AND zero/absent completion tokens. A lone signature (thoughtSignature, Claude signature/signature_delta, Interactions hasSignature()) is not content.
  • Stream path (conductor_stream.go): bootstrap detector, drain on terminal emptiness, first-chunk/establishment timeout. In-band provider errors are finalized at EOF (finish()).
  • Non-stream paths (conductor_execution.go, Home, Antigravity credits): same judgment; empty is recorded only as failure (markEmptyCompletion).
  • Credential matcher: invalid_api_key / api_key_invalid / "API key not valid" is credential-scoped, not a request fault.
  • Log redaction: summarizeErrorForLog redacts sk-… / Bearer / labeled secrets.

No selector, session affinity, pluginhost, HTTP handler, or registry-resume changes in this PR.

History

Previous head 239f5fb9 also carried selector/affinity/pluginhost/registry/handler scope (~61 files, +15953). That volume was force-pushed out of #4881; it lands in a follow-up PR after this one (serialized with overlapping cooldown/selector work).

Test plan

  • go build ./...
  • go test -count=1 ./sdk/cliproxy/auth/ — 1417 passed, including TestExecuteSignatureOnlyRotatesAuth (HTTP 200, signature-only, 0 tokens → rotate)

@github-actions
github-actions Bot changed the base branch from main to dev August 10, 2026 15:38
@github-actions

Copy link
Copy Markdown

This pull request targeted main.

The base branch has been automatically changed to dev.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d82c51cf12

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 426e5b5e03

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_execution.go
Comment thread sdk/cliproxy/auth/empty_completion.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d447213f05

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated
@warelik

warelik commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review — all four points addressed in the latest commits:

  1. Post-translation shapes: predicates now also recognize Claude and Gemini response shapes (empty Claude message / empty Gemini candidates), covering the translated-payload case.
  2. Home non-stream path: executeHome now applies the same empty-completion judgment.
  3. Gemini SAFETY stops: finishReason is gated — only STOP (or absent) counts toward empty-terminal; SAFETY/RECITATION/MAX_TOKENS/etc. are treated as blocked and pass through without cooling the auth.
  4. OpenAI content_filter/length: same gating applied to the OpenAI branch — non-stop terminal reasons are not judged empty and do not quarantine the credential.
    Additionally covered in this revision: Responses-API (codex/xai) stream/payload recognition and the plugin-executor path (stream + non-stream).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f6e81b5a5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread internal/pluginhost/executor_route.go Outdated
Comment thread sdk/cliproxy/auth/empty_completion.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d0ea62260

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated
Comment thread sdk/cliproxy/auth/empty_completion.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 301b7b5026

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated
Comment thread sdk/cliproxy/auth/empty_completion.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe5b7c96e8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a5cee57cc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c873518b30

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_stream.go Outdated
Comment thread sdk/cliproxy/auth/selector.go Outdated
@warelik

warelik commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4485f59a2f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated
@warelik

warelik commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12a2c546c3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/selector.go Outdated
@warelik

warelik commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 691f2a9f29

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated
@warelik

warelik commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5420c1211f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go Outdated
Comment thread sdk/cliproxy/auth/selector.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 635c340033

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/route_tracker.go Outdated
@warelik

warelik commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Current-head summary (@27667c9d) — CPA follow-up review context

This revision adds four accepted logical commits on top of the existing empty-completion retry feature, now merged with latest origin/dev (27667c9d). All review threads from prior heads are addressed as follows:

Route-exhaustion header preservation (P2 — now fixed)

The wrapper no longer drops upstream header carriers. routeExhaustionClonedError implements Headers() http.Header (first/outermost carrier via errors.As, fresh cloneHTTPHeader copy) and nil-safe SafeResponseHeaders(). Retry-After/x-request-id survive the route-summary wrapper. Commit c5ad1aca, tests TestRouteExhaustion_HeadersForwarded/AbsentNil/NilReceiver/NestedOutermostWins/ExecuteStreamHeaders. Reply: discussion_r3776637460.

Out-of-scope threads (empty_completion.go / selector.go)

Two remaining P2 threads target the empty-completion feature (empty_completion.go SSE aggregation, selector.go affinity metadata), unchanged by this follow-up; replies note them as owned by the feature work: discussion_r3776641238, discussion_r3776641543.

New fixes in this revision

  • 67b7c336 fix(auth): rotate invalid upstream credentials — classifies 403 authentication_error bodies (Codex invalid/expired key) as credential failure; internal/clienterror suite green.
  • 79bfa03f fix(openai): redact upstream error secrets — centralizes sanitization across all OpenAI streaming/non-stream sinks (chat/completions, Responses, images, videos); when a sanitized error is the whole response, body is nil and failure emitted via status so no secret survives; security-boundary commit.
  • 247dbf4e style(auth): format completion fixtures — gofmt-only.

Verification (head 27667c9d)

  • gofmt -l on all changed files: empty.
  • Focused: go test -count=1 ./internal/clienterror/ ./sdk/api/handlers/openai/ ./sdk/cliproxy/auth/ → ok; focused -race suites → ok.
  • Full: go build ./... clean; go vet ./... has 5 warnings provably identical on base origin/dev (Worktree base check) — pre-existing, none in changed files.
  • go test ./... only pre-existing out-of-partition failures: internal/util no-copy byte-write scan surfacing third-party circl module-cache writes (owned by separate worktree, independent CLEAN gate) and the documented gin.SetMode parallel-test race in openai_responses_multi_agent_test.go (byte-identical to base).
  • Translator invariant: git diff origin/dev -- internal/translator/ is empty.
  • PR head == local HEAD == fork remote == 27667c9d; mergeable MERGEABLE.

@warelik

warelik commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 27667c9da8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_stream.go Outdated
@warelik

warelik commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Current-head summary (@e59758d) — surface errors before stream DONE

e59758d601aaf224def4082355719b3d838bc077 fixes the confirmed P2 thread "Check pending stream errors before sending DONE" (discussion_r... on /v1/chat/completions / /v1/completions).

Change

Both OpenAI initial streaming peek loops now consume a buffered pending error before committing SSE headers:

  • handleStreamingResponse (chat) — sdk/api/handlers/openai/openai_handlers.go
  • handleCompletionsStreamingResponse (legacy) — same file

When handlers.PendingStreamError(errChan) reports a pending error, the handler writes the sanitized error response and returns without 200/data: [DONE]. Clean close with no pending error still emits [DONE]. No shared-helper refactor; ForwardStream and the channel producer are untouched.

Deterministic actual-handler regressions (real executor + registered auth)

TestStreamingPeekConsumesBufferedPendingError (chat + legacy): buffered errChan error + closed dataChan → error status (never 200), body lacks data: [DONE], raw secret fixture redacted ([REDACTED] present, secret absent).
TestStreamingPeekCleanCloseStillEmitsDone (control, chat + legacy): valid content chunk then clean close → 200 + data: [DONE].

Verification (head e59758d)

  • Focused new tests: PASS normal, PASS -race x2.
  • go test -count=1 ./sdk/api/handlers/... : ok (handlers, claude, gemini, openai).
  • go build ./... clean; go vet ./sdk/api/handlers/openai/ clean.
  • -race multi-agent failures (TestPrepareCodexMultiAgentV2ToolsAtResponsesBoundary, TestResponsesPreparesCodexMultiAgentV2ToolsForHTTPAndSSE) reproduce at base 27667c9d without these changes — pre-existing, unrelated.
  • PR head == local HEAD == fork remote == e59758d; mergeable MERGEABLE.
  • Working tree clean (only pre-existing untracked caches + protected .bak).

Replies: discussion_r... (thread reply with details), discussion_r... (route-exhaustion). Thread resolved.

@warelik

warelik commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e59758d601

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/api/handlers/openai/openai_responses_handlers.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12c609f78c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/selector.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7b1c2aff8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_stream.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22deb3eb23

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread internal/pluginhost/executor_route.go Outdated
Comment thread internal/pluginhost/executor_route.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89e87a859f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_stream.go
Comment thread sdk/cliproxy/auth/empty_completion.go
@warelik
warelik force-pushed the fix/empty-completion-retry branch from 89e87a8 to 5c6295e Compare August 23, 2026 14:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c6295e5a1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_stream.go Outdated
@warelik
warelik force-pushed the fix/empty-completion-retry branch 2 times, most recently from 067ec47 to a3bd120 Compare August 23, 2026 14:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3bd120da6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_cooldown.go Outdated
@warelik
warelik force-pushed the fix/empty-completion-retry branch 2 times, most recently from 0953456 to c982b65 Compare August 23, 2026 15:07
@warelik
warelik force-pushed the fix/empty-completion-retry branch from c982b65 to bbccdfa Compare August 23, 2026 15:25

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04ae4ef71e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_cooldown.go
Comment thread internal/pluginhost/executor_route.go
warelik added a commit to warelik/CLIProxyAPI that referenced this pull request Aug 23, 2026
The previous revert of sdk/cliproxy/auth to origin/dev removed changes
that belong to PR router-for-me#5150. Restore only the replay-alias/session-cache
parts from 9031522:

- sdk/cliproxy/auth/session_cache.go: all router-for-me#5150 additions
  (RestoreAliasesIfAbsent, SetAliasesIfAllAbsent, SetAliasesIfNoConflict,
  ReplaceAliasesIfUnchanged, compact helpers) used for replay alias binding.
- sdk/cliproxy/auth/selector_test.go: only the session-cache unit tests
  (SetAliasesIfAllAbsent, SetAliasesIfNoConflict, ReplaceAliasesIfUnchanged).
  Left the SessionAffinitySelector.Pick alias-rebind tests and selector.go
  changes out because they overlap with router-for-me#4881.

Build and go test ./... pass.
@warelik
warelik force-pushed the fix/empty-completion-retry branch 2 times, most recently from 13dd4c9 to 2e37241 Compare August 23, 2026 16:08
warelik added a commit to warelik/CLIProxyAPI that referenced this pull request Aug 23, 2026
Finding from router-for-me#4881 review: internal/pluginhost/executor_route.go:248
`discardStreamChunks` started a goroutine that ranged over the source
channel forever. If a plugin left the channel open after an empty terminal
frame, the drainer goroutine never exited, causing a cumulative leak in a
long-lived proxy.

- Pass the request context into `discardStreamChunks`.
- Add `streamDrainTimeout` (5s default); the drainer exits on context
cancellation, on timeout, or when the source channel closes.
- Reset the drain timeout on each received chunk so slow trailing chunks are
still drained, but the goroutine is always bounded.
- Add `TestDiscardStreamChunksExitsOnContextCancel` and
`TestDiscardStreamChunksExitsOnOpenUnclosedChannel` proving the goroutine
exits even when the source channel is never closed.
- Update reports/4881-rebuild.md with the take/no-take findings and router-for-me#5130 status.
`discardStreamChunks` started a goroutine that ranged over the source
channel forever. If a plugin left the channel open after an empty terminal
frame, the drainer never exited, causing a cumulative leak in a long-lived
proxy.

- Pass the request context into `discardStreamChunks`.
- Add `streamDrainTimeout` (5s default); the drainer exits on context
cancellation, on timeout, or when the source channel closes.
- Reset the drain timeout on each received chunk so slow trailing chunks are
still drained, but the goroutine is always bounded.
- Add `TestDiscardStreamChunksExitsOnContextCancel` and
`TestDiscardStreamChunksExitsOnOpenUnclosedChannel` proving the goroutine
exits even when the source channel is never closed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e37241300

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/empty_completion.go
@warelik
warelik force-pushed the fix/empty-completion-retry branch from 5d35e9d to a995f7b Compare August 23, 2026 16:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a995f7b18a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_stream.go Outdated
… and timeout

`sdk/cliproxy/auth/conductor_stream.go` had its own `discardStreamChunks`
variant that started a goroutine ranging over the source channel forever.
Without a context, a plugin that left the channel open leaked a goroutine
per stream.

- Add `ctx` parameter and `streamDrainTimeout` (5s default) to the auth
`discardStreamChunks`, mirroring the pluginhost fix.
- The drainer exits on context cancellation, on timeout, or when the source
channel closes; the timeout resets on each received chunk so slow trailing
chunks are still drained.
- Update all call sites in `conductor_stream.go` to pass the request context.
- Add `TestDiscardStreamChunksExitsOnContextCancel` and
`TestDiscardStreamChunksExitsOnOpenUnclosedChannel` in
`conductor_stream_test.go`.

Also removes the transient-429 changes from the router-for-me#4881 branch; that finding
belongs on router-for-me#5130 (fix/quota-backoff-hint-floor).
Keep router-for-me#4881 TTFT attempt context. Apply router-for-me#5211
newUpstreamAttemptContext on each stream model
attempt and unauthorized refresh retry.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

payload := rewriteForceMappedStreamChunk(rewriter, chunk.Payload)
if len(payload) == 0 {
return true
}
chunk.Payload = payload

P1 Badge Sanitize post-bootstrap error payloads before forwarding

When an in-band provider error arrives after meaningful output has committed the stream, this branch sanitizes only the parsed streamErr; chunk.Payload remains unchanged and is subsequently forwarded to the downstream client. An error such as {"error":{"message":"Incorrect API key provided: sk-live-secret"}} therefore exposes the proxy's upstream credential to the caller. Fresh evidence beyond the earlier result-recording fix is that the post-bootstrap path passes the original payload through rewriteForceMappedStreamChunk; sanitize or reconstruct the error frame before emitting it.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor_execution.go Outdated
Error-path stream chunks still forwarded raw credentials after
the parsed *Error was sanitized. Apply the same recognition set
to payloads, and match prose "API key provided" plus vendor
prefixes, not only sk-.
@warelik

warelik commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

P1-A (review body 5011224530 — no thread, resolve does not apply)

Fixed in bc92dcc.

Carrier of this finding is the review body, not a review thread. A reply here is the only applicable response.

wrapStreamResult.emit already sanitized the parsed *Error for logs/LastError/result hooks, then forwarded the original chunk.Payload through rewriteForceMappedStreamChunk into out. That is the caller-visible leak.

Every send into out from emit now redacts the payload once the chunk is on an error path (chunk.Err or streamPayloadErrorDetector.Observe):

  • out <- chunk when chunk.Err != nil (payload on the error chunk, if any)
  • out <- chunk after rewriteForceMappedStreamChunk (post-bootstrap in-band error, and any later payload while failed)
  • rewriter tail also goes through emit, so it hits the same failed redact
  • streamErrorResult sanitizes Err before the single error chunk is emitted (no payload)

wrapHomeStream is an outer passthrough of wrapStreamResult and does not re-emit unsanitized bytes.

The pluginhost twin of this path — wrapStreamEmptyCompletion, which after meaningful output forwarded remaining payloads without going through wrapStreamResult — now uses the same recognition set (RedactSecrets / StreamPayloadErrorDetector) at its single forward() choke point. Same class as the two discardStreamChunks functions: fixing only the conductor wrapper would have left the plugin wrapper leaking.

@warelik

warelik commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Pushed the split in-band error frame fix:

  • Added streamPayloadErrorDetector.TakeFrame() / HasPending() so callers can buffer until a complete frame is recognized.
  • conductor_stream.go and executor_route.go now hold post-bootstrap SSE/JSON fragments and emit a fully redacted aggregate only after the error frame completes.
  • Non-frame payloads (opaque text, antigravity fallback, etc.) still pass through immediately.
  • Added regression tests for split errors in both auth and plugin paths.

go build ./cmd/server, go test -count=1 -timeout 120s ./sdk/cliproxy/auth ./internal/pluginhost pass (one unrelated TTFT test was flaky and passed on re-run).

warelik added a commit to warelik/CLIProxyAPI that referenced this pull request Aug 25, 2026
S3 Claude empty detector treated the failover stream as empty_completion.
Parent router-for-me#4881 fixture already carries content_block_start text:ok.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant