Skip to content

Review only: HOUSE-79 upstream v8.0.44, part 1 of 13: auth routing, affinity, quota, cooldown - #14

Closed
ericlitman wants to merge 1 commit into
review/house79/base-01from
review/house79/chunk-01
Closed

ericlitman wants to merge 1 commit into
review/house79/base-01from
review/house79/chunk-01

Conversation

@ericlitman

@ericlitman ericlitman commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Review only. Do not merge. This PR is one part of the review of #13, the merge of upstream v8.0.44 into the fork. That PR's diff (966 files) is too large for one review round.

  • This part covers: auth routing, affinity, quota, cooldown.
  • The head tree is identical to Merge upstream v8.0.44 into the fork (HOUSE-79) #13 head tree (c88d701f7bc0894cde517ca7f931f5c05749cd74). The base is that tree with only this part's files restored to main (c265779f), so the diff is this part's files alone and the surrounding code is the real final code.
  • The 13 parts together cover every production file the sync changes (410 paths). Tests, golden data, generated model catalogs, images, docs and go.sum are in no part's diff.
  • The code is upstream's, except where our fork's soonest-reset selector, quota observations, quota reserves, usage reads and Terra injection touch it. Review it as code we are about to deploy to the proxy every one of our agents uses: report defects that would affect our deployment.

Linear: HOUSE-79

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@mergify

mergify Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@unfret

unfret Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🛑 8 blocking findings

🟡 Medium · Store-adopted credentials keep the old version

🟡 Medium · Require observations to postdate the cooldown write

🟡 Medium · Delayed records replace a newer independent cooldown

🟡 Medium · SIWC expiry blocks valid Codex-client credentials

🟡 Medium · Treat future credit-hint timestamps as stale

🟡 Medium · Quota restoration clears unrelated legacy model limits

🟡 Medium · Successful refresh retains the old 401 deadline

🟡 Medium · Do not clear failures for an unrefreshed token partition

Walkthrough

This review-only slice of the upstream v8.0.44 sync brings in the new auth routing, cooldown and persistence machinery that every proxied request goes through. Credentials now carry a version, an execution sequence and rejected-token tracking, so stale results and late successes can't clear newer cooldowns or 401s. Saves happen under a per-credential mutation gate that Load waits on. Devin credential-quota cooldowns gain a two-layer model, with a persisted independent shadow and early restoration from refresh observations. Codex gains SIWC (Sign in with ChatGPT) nested credentials with separate refresh and persistence. Session affinity can keep a binding across transient failures. Eight medium findings question version bumps, observation ordering, shadow merging, SIWC expiry for Codex clients, credit-hint freshness and post-refresh failure clearing.

Area What changes Review notes
Cooldown and result accounting Results carry credential version, registration epoch, access token and dispatch sequence. Stale results are dropped, and successes dispatched before a cooldown can't clear it. Stream truncation, HTTP/2 close and TLS handshake timeouts no longer cool credentials, and canceled requests still record upstream Retry-After cooldowns. Delayed records replace a newer independent cooldown
Devin credential quota Model states keep an independent cooldown layer beneath a propagated credential_quota, and that layer is persisted in cooldown records. Exhausted Devin credentials revalidate hourly, and a newer observation showing daily and weekly quota remaining releases the cooldown early. Require observations to postdate the cooldown write
Quota restoration clears unrelated legacy model limits
Persistence and lifecycle A per-auth mutation gate and a reload semaphore serialize Register, Update, Modify, MarkResult and Load. Store saves run on a clone outside m.mu and merge their changes back by reflection. File-snapshot digests reject stale file reloads, a credential-change watch API is added, and best-effort metadata saves can be preempted. Store-adopted credentials keep the old version
Refresh and 401 handling A 401 records the rejected access token and schedules a refresh. Refreshes that commit after another update are discarded unless a Codex patch applies, and canceled refreshes reschedule. Codex gets a 96h outer refresh lead and SIWC-specific refresh, binding and file-locked persistence; Codex client user agents bypass SIWC. SIWC expiry blocks valid Codex-client credentials
Successful refresh retains the old 401 deadline
Do not clear failures for an unrefreshed token partition
Selection and affinity Adds a 'quota' rebind mode that keeps a session bound through transient failures, and guardian subagents inherit their parent's latest binding. Plugin scheduler selections get a selection ID propagated to usage, and scheduler rejections stop the request. Antigravity credits re-check unavailable hints older than 10 minutes. Treat future credit-hint timestamps as stale
Since the last review
  • New: Store-adopted credentials keep the old version
  • New: Require observations to postdate the cooldown write
  • New: Delayed records replace a newer independent cooldown
  • New: SIWC expiry blocks valid Codex-client credentials
  • New: Treat future credit-hint timestamps as stale
  • New: Quota restoration clears unrelated legacy model limits
  • New: Successful refresh retains the old 401 deadline
  • New: Do not clear failures for an unrefreshed token partition
Repair prompt

Read the Unfret check run for mobilyze-llc/CLIProxyAPI#14 at head 26ba47c3256ad1454cbe4619a57d13a75dd5d762 (run panel:26ba47c3256ad1454cbe4619a57d13a75dd5d762:THo-XFkWla_:KJD-puSwdJg, check). Use the check's App id and GET /repos/mobilyze-llc/CLIProxyAPI/check-runs/122286788209; its output.text contains the structured result. Compare it with the current code and head, verify that each listed mandatory finding still applies, repair only those that do, honor the repository constraints, and validate the changes. Leave advisories and accepted risks alone. A manually resolved thread is not a waiver.

Details
Lane / stage Model Effort Outcome Duration
broad-gpt-5.6-sol-xhigh / review openai/gpt-5.6-sol → gpt-5.6-sol · primary xhigh succeeded 13 min 48 s
broad-opus-5.5-max / review Not recorded Not recorded failed Not recorded
focal-gpt-5.6-sol-xhigh copy / review openai/gpt-6.1-sol → gpt-6.1-sol · primary xhigh succeeded 17 min 12 s
focal-gpt-5.6-sol-xhigh copy 2 / review openai/gpt-6.1-sol → gpt-6.1-sol · primary xhigh succeeded 13 min 39 s
judge / judge openai/gpt-6-astra → gpt-6-astra · primary high succeeded 2 min 37 s
confirmation / confirmation anthropic/claude-opus-5-5 → claude-opus-5-5 · primary max succeeded 3 min 22 s

First review of this pull request.
3 reviewers finished.
Commit 26ba47c.
Check run.
Run: GET /unfret/run/panel:26ba47c3256ad1454cbe4619a57d13a75dd5d762:THo-XFkWla_:KJD-puSwdJg.

@unfret review · @unfret status · @unfret help

@unfret unfret 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.

🛑 8 blocking findings
🟡 Medium · Store-adopted credentials keep the old version
🟡 Medium · Require observations to postdate the cooldown write
🟡 Medium · Delayed records replace a newer independent cooldown
🟡 Medium · SIWC expiry blocks valid Codex-client credentials
🟡 Medium · Treat future credit-hint timestamps as stale
🟡 Medium · Quota restoration clears unrelated legacy model limits
🟡 Medium · Successful refresh retains the old 401 deadline
🟡 Medium · Do not clear failures for an unrefreshed token partition
Check

m.mu.Unlock()
err := m.persist(ctx, saved)
m.mu.Lock()
mergeAuthSaveDelta(auth, before, saved, true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Store-adopted credentials keep the old version

PersistCodexAuthFile can adopt tokens rotated by another process during an ordinary metadata save. This merge publishes those tokens without advancing CredentialVersion, which updateInternalLocked calculated before saving. For example, a note-only Update adopts token B from disk but retains token A's version; a delayed 401 from A then passes isStaleExecutionResult and imposes a 30-minute model cooldown on healthy B. Credential changes introduced by persistence need the same version fencing as other replacements. Also reported for this defect: - Advance credential version when persistence adopts disk tokens (sdk/cliproxy/auth/conductor_persistence.go:230): PersistCodexAuthFile can adopt tokens rotated by another process during an ordinary save. This merge publishes those replacement tokens without advancing CredentialVersion. An in-flight request using the old token consequently retains the current version, passes isStaleExecutionResult, and can apply its old-token 401 to the replacement credential, imposing a 30-minute model cooldown. This is reproducible by rotating the file, recording another request's success to adopt it, then recording the old request's 401.

if hasUnauthorizedAuthFailure(existing) {
return false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Require observations to postdate the cooldown write

The restoration check is documented to require an observation strictly newer than the exhaustion write, but Before accepts ObservedAt == existing.UpdatedAt. With coarse or replayed timestamps that are newer than the previous quota snapshot but equal to the cooldown write, positive signals incorrectly clear the Devin credential and model cooldowns before there is post-failure evidence of recovery.

Comment on lines +443 to +446
if preferred == nil || (liveShadow != nil && !liveShadow.UpdatedAt.After(updatedAt)) {
preferred = recordShadow
} else if liveShadow != nil && liveShadow.UpdatedAt.After(updatedAt) {
preferred = liveShadow

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Delayed records replace a newer independent cooldown

When an auth-level credential quota coexists with a live unwrapped model cooldown, liveShadow captures that newer model state above. However, state.credentialQuotaShadow is still nil, so this condition selects recordShadow without comparing timestamps. Restoring an older one-hour record over a newer 12-hour independent block consequently saves the one-hour shadow; subsequent Devin quota restoration reinstates it and releases the model 11 hours early. The captured live layer must participate in the newest-wins selection even when no shadow previously existed.

Comment on lines +409 to +410
if IsCodexUseSIWC(auth) {
return authSIWCTokenField(auth, "access_token")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · SIWC expiry blocks valid Codex-client credentials

With use_siwc enabled, an expired or rejected SIWC token now makes AccessTokenExpirationTime report the credential as expired. Both legacy selection and the scheduler use that context-free check, so a Codex client receives auth_unavailable even when its outer OAuth token remains valid. This contradicts IsCodexUseSIWCForRequest and the executor, which explicitly use outer credentials for Codex clients. Reproduced with a codex_cli_rs User-Agent, a valid outer token, and expired SIWC credentials. Also reported for this defect: - Use the request's credential partition during selection (sdk/cliproxy/auth/conductor_refresh.go:409-410): With use_siwc enabled, a valid outer token, and an expired or rejected SIWC token, Codex-client requests fail before reaching the executor. Both selector paths call AccessTokenExpirationTime(), which now reads the SIWC token through this context-free helper, despite IsCodexUseSIWCForRequest explicitly requiring Codex clients to use outer credentials. A request with User-Agent codex-tui/0.154.0 therefore returns auth_unavailable even though its required outer token is valid. - Do not block Codex clients on SIWC expiration (sdk/cliproxy/auth/scheduler.go:897): With use_siwc=true, AccessTokenExpirationTime evaluates the nested SIWC token, but IsCodexUseSIWCForRequest explicitly routes Codex user agents through the outer token. This request-agnostic scheduler check therefore removes the credential when SIWC is expired or rejected even if the outer token is valid, causing the default fast path to return no auth for otherwise valid Codex-client requests.

Also affects:


// IsAntigravityCreditsHintFresh reports whether a known credits hint is still
// within AntigravityCreditsHintRefreshInterval of now.
func IsAntigravityCreditsHintFresh(hint AntigravityCreditsHint, now time.Time) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Treat future credit-hint timestamps as stale

If a hint is written by a node whose clock is ahead, now.Sub(hint.UpdatedAt) is negative and therefore always below the refresh interval. A known-unavailable hint can consequently exclude an Antigravity credential without revalidation until the local clock passes the future timestamp plus ten minutes, yielding a false no-fallback result when it is the only candidate.

Comment on lines +698 to +699
if !state.Quota.Exceeded || (reason != "quota" && !wrapped) {
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Quota restoration clears unrelated legacy model limits

RestoreCooldownStates loads model records before the auth-level record, so legacy records can leave a genuine model-scoped 429 as an unwrapped state with reason quota and no shadow. This condition admits every such state into restoration, regardless of whether its error describes daily/weekly exhaustion. A fresh positive Devin quota observation therefore resets an unrelated, still-live model rate limit. Reproduced with a 12-hour model rate-limit record and a separate credential_quota record.

Comment on lines +785 to +791
if state.Quota.Exceeded && (state.Quota.NextRecoverAt.IsZero() || state.Quota.NextRecoverAt.After(now)) {
state.LastError = nil
if strings.Contains(strings.ToLower(state.StatusMessage), "unauthorized") || isInvalidGrantErrorMessage(state.StatusMessage) {
state.StatusMessage = state.Quota.Reason
}
state.UpdatedAt = now
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Successful refresh retains the old 401 deadline

If a model receives a 10-second quota cooldown followed by an in-flight 401, MarkResult extends NextRetryAfter to 30 minutes while leaving Quota.NextRecoverAt at 10 seconds. After credential replacement, this branch clears the unauthorized error but retains its 30-minute retry deadline. The model consequently remains blocked long after its legitimate quota cooldown expires. Reproduced through MarkResult followed by UpdateRefreshedAuth.

if updated.Status == StatusError || updated.Status == "" {
updated.Status = StatusActive
applyPreservedRejectedTokens(auth, updated, true)
if updated.RejectedAccessToken == "" || authAccessToken(updated) != updated.RejectedAccessToken {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Do not clear failures for an unrefreshed token partition

When the outer token is rejected and a refresh rotates only SIWC, applyPreservedRejectedTokens intentionally retains RejectedAccessToken; however this condition compares that outer token with authAccessToken(updated), which resolves to SIWC, and clears the auth error/unavailable state. Equivalent predicates in the merge, lifecycle, and SIWC-bind paths have the same result, so the scheduler can route Codex clients back to the unchanged rejected outer token and produce recurring 401s.

@ericlitman

Copy link
Copy Markdown
Contributor Author

Closing: the review scope changed. Eric (2026-10-27): "i don't want to fix anything that's an issue in upstream - our only goal here is to validate our changes". Upstream code is out of scope. Our fork's own changes on v8.0.44 get one review PR instead. Findings from this part are recorded in the HOUSE-79 adjudication record. None was a defect in our code.

@ericlitman ericlitman closed this Oct 27, 2026
@ericlitman
ericlitman deleted the review/house79/chunk-01 branch October 27, 2026 19:16
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