Skip to content

Review only: HOUSE-79 upstream v8.0.44, part 12 of 13: everything else - #24

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

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

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: everything else.
  • 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

🛑 9 blocking findings

🔴 Critical · Preserve refreshes from the model-fetch command

🔴 High · Retain a working discovery receive path on Windows

🟡 Medium · Atomic saves sever credential symlinks

🟡 Medium · Quota-cooled model hidden when another credential has a non-quota suspension

🟡 Medium · Explicit login cannot replace a corrupt credential file

🟡 Medium · ToolSearch escapes registry clone isolation

🔵 Low · Use suffixed errors instead of shadowing the parser error

🔵 Low · Check deferred probe socket close errors

🔵 Low · Check deferred credential-temp cleanup errors

Walkthrough

This PR is part 12 of 13 of the review-only split of the upstream v8.0.44 merge (#13), and it covers the files no other part claims. The largest pieces are a rewrite of Codex credential persistence, a hand-built mDNS responder and browser replacing zeroconf, and configurable model catalog sources with registry changes to quota-cooldown accounting. It also adds smaller items: a GitHub token setting, request-log path hardening, context-aware git store operations, Antigravity UA/version bumps, and xAI TTS built-ins. It bumps Go to 1.26.10, and no tests are in this part's diff.

Area What changes Review notes
Codex credentials and Sign in with ChatGPT Adds the SIWC OAuth flow and refresh. Codex token saves now take a per-file mutex plus an inter-process flock or LockFileEx, merge with the on-disk JSON, apply partition-specific refresh patches, and write atomically via temp-file rename. The git, object, and Postgres stores route Codex auths through a shared persist helper. Preserve refreshes from the model-fetch command
Atomic saves sever credential symlinks
Explicit login cannot replace a corrupt credential file
Check deferred credential-temp cleanup errors
LAN discovery (mDNS/DNS-SD) Replaces zeroconf registration and browsing with custom responder and browse-cache code: host probing and conflict handling, known-answer suppression, and goodbye packets. It also moves the TXT records to the draft-su-ai-gateway-discovery-00 schema, keeps parsing of legacy records, and derives the advertised protocols from subtypes. Retain a working discovery receive path on Windows
Use suffixed errors instead of shadowing the parser error
Check deferred probe socket close errors
Model registry and catalogs Lets the general, Codex, and Devin catalogs come from a configurable URL or absolute path, using a generation-fenced updater; Home mode never loads the general or Devin catalog. Adds epoch-conditional register and replace calls that can preserve scheduling state. Quota and credential_quota suspensions now count toward model availability. Adds ToolSearch capability gating for supports_search_tool and Claude dated/bracketed family lookup. Quota-cooled model hidden when another credential has a non-quota suspension
ToolSearch escapes registry clone isolation
Config, logging, and misc Documents new config keys in config.example.yaml, including body limits, response compression, TLS handshake timeout, session-affinity rebind policy, Claude tool-search, and Codex compression. Other changes: - Adds a Claude web-search result cache and an Antigravity interactions cache. - Maps MaxBytesError to 413 and treats Claude thread-not-found as a client error. - Adds error-aware flushing. - Hardens request-log filenames and strips OAuth query strings from management access logs. - Adds native-key and service-tier fields to the redis usage queue.
Since the last review
  • New: Atomic saves sever credential symlinks
  • New: Quota-cooled model hidden when another credential has a non-quota suspension
  • New: Retain a working discovery receive path on Windows
  • New: Use suffixed errors instead of shadowing the parser error
  • New: Check deferred probe socket close errors
  • New: Explicit login cannot replace a corrupt credential file
  • New: Check deferred credential-temp cleanup errors
  • New: Preserve refreshes from the model-fetch command
  • New: ToolSearch escapes registry clone isolation
Repair prompt

Read the Unfret check run for mobilyze-llc/CLIProxyAPI#24 at head 2a468f20a71c87080175898e06e933644f28fc8b (run panel:2a468f20a71c87080175898e06e933644f28fc8b:-yg6q3mpa3k:Zm2lO7-MHGA, check). Use the check's App id and GET /repos/mobilyze-llc/CLIProxyAPI/check-runs/122384711475; 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 50 s
broad-opus-5.5-max / review anthropic/claude-opus-5-5 → claude-opus-5-5 · primary max succeeded 19 min 16 s
focal-gpt-5.6-sol-xhigh copy / review openai/gpt-6.1-sol → gpt-6.1-sol · primary xhigh succeeded 15 min 25 s
focal-gpt-5.6-sol-xhigh copy 2 / review openai/gpt-6.1-sol → gpt-6.1-sol · primary xhigh succeeded 16 min 45 s
judge / judge openai/gpt-6-astra → gpt-6-astra · primary high succeeded 2 min 56 s
confirmation / confirmation anthropic/claude-opus-5-5 → claude-opus-5-5 · primary max succeeded 2 min 22 s

First review of this pull request.
4 reviewers finished.
Commit 2a468f2.
Check run.
Run: GET /unfret/run/panel:2a468f20a71c87080175898e06e933644f28fc8b:-yg6q3mpa3k:Zm2lO7-MHGA.

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

🛑 9 blocking findings
🔴 Critical · Preserve refreshes from the model-fetch command
🔴 High · Retain a working discovery receive path on Windows
🟡 Medium · Atomic saves sever credential symlinks
🟡 Medium · Quota-cooled model hidden when another credential has a non-quota suspension
🟡 Medium · Explicit login cannot replace a corrupt credential file
🟡 Medium · ToolSearch escapes registry clone isolation
🔵 Low · Use suffixed errors instead of shadowing the parser error
🔵 Low · Check deferred probe socket close errors
🔵 Low · Check deferred credential-temp cleanup errors
Check

Comment on lines +97 to +99
cleanupTmp = false
return nil
}

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 · Atomic saves sever credential symlinks

For a credential accessed through a file symlink, canonicalLockPath locks the resolved target, but this rename replaces the symlink itself. Saving or refreshing then creates a separate credential copy while leaving the original target stale; other processes using that target continue using the old tokens. The previous os.Create-based save followed the symlink. Also reported for this defect: - Keep symlinked credentials on their canonical target (internal/auth/codex/token.go:94-96): FileTokenStore.List and resolveCodexAuthDiskPath accept and follow symlinked credential files, and WithFileLock locks their canonical target. This rename instead replaces the symlink itself. After a save or refresh, CPA and consumers of the original target have separate credential files, with the original retaining stale tokens; subsequent saves also stop sharing the same lock. This breaks previously working shared-credential setups.

Also affects:

// A quota cooldown can mark the same client both quota-exceeded and
// suspended. Count that unavailable client only once.
effectiveClients := availableClients - expiredClients - otherSuspended + quotaAndOtherSuspended
effectiveClients := availableClients - expiredClients - cooldownSuspended - otherSuspended + quotaAndSuspended

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-cooled model hidden when another credential has a non-quota suspension

The new effectiveClients now subtracts cooldownSuspended, but the fallback available clause still requires otherSuspended == 0. The same change appears in GetAvailableModelsByProvider at line 1588. Scenario: - A model is served by credentials A and B. - A is suspended with reason "quota" and its 5-minute QuotaExceeded window has lapsed (long Claude/Codex cooldowns). - B is suspended for a non-quota reason, such as unauthorized. Old code: effective = 2-0-1 = 1, so the model was listed. New code: effective = 2-0-1-1+0 = 0, and otherSuspended == 1 fails the fallback clause, so the model is hidden. With A alone, the model stays listed. Adding a broken credential B therefore removes it. This contradicts the stated isQuotaSuspension intent that quota-cooled models stay listed and get a 429 instead of looking unknown. Consequence: GetAvailableModels (/v1/models and the Codex client catalog), GetAvailableModelInfos and GetAvailableModelsByProvider drop the model, while requests still route to it.

Comment on lines +477 to +482
if err := conn4.SetControlMessage(ipv4.FlagInterface|ipv4.FlagDst, true); err != nil {
err4 = err
if errClose := conn4.Close(); errClose != nil {
log.WithError(errClose).Debug("discovery: close unusable IPv4 browse socket")
}
conn4 = nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 High · Retain a working discovery receive path on Windows

In the pinned golang.org/x/net version, both ipv4.SetControlMessage and ipv6.SetControlMessage unconditionally return errNotImplemented on Windows. These new checks therefore close both successfully opened sockets, making every Windows browse fail with "open mDNS sockets" before sending a query. The previous zeroconf browser did not make unsupported control-message metadata fatal. Windows needs a supported receive-isolation implementation rather than unconditional socket rejection.

}

if answerTypeEnum {
if err := builder.PTRResource(dnsmessage.ResourceHeader{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Low · Use suffixed errors instead of shadowing the parser error

This new err binding shadows the parser error in buildServiceQueryReplyFiltered, violating AGENTS.md's explicit suffixed-variable rule. The identical shadowing pattern also occurs at lines 797, 807, 817, 825, and 841; these local error bindings need distinct suffixed names. Also reported for this defect: - Use operation-specific names for shadowed reply errors (internal/discovery/zeroconf.go:787-792): This declaration shadows the parser's existing err variable in buildServiceQueryReplyFiltered, violating AGENTS.md's mandatory operation-suffixed naming rule for shadowed variables. The same pattern occurs at lines 797, 807, 817, 825, and 841. Use operation-specific error names consistently at these sites.

Also affects:

var conns []probeConn
defer func() {
for _, c := range conns {
_ = c.conn.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Low · Check deferred probe socket close errors

The new deferred cleanup discards every socket Close error, contrary to AGENTS.md's requirement to check and log deferred errors. Unexpected cleanup failures therefore have no diagnostic. Expected already-closed errors can be distinguished from other failures. Also reported for this defect: - Check errors from deferred probe cleanup (internal/discovery/zeroconf.go:2183): The new deferred cleanup discards socket Close errors, contrary to AGENTS.md's mandatory requirement to check and log deferred cleanup errors. A cleanup failure is therefore silently suppressed. Check the returned error and log unexpected failures, excluding an expected already-closed socket where appropriate.

Comment on lines +288 to +289
if errUnmarshal := json.Unmarshal(existingBytes, &diskMap); errUnmarshal != nil || diskMap == nil {
return fmt.Errorf("invalid existing credential JSON")

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 · Explicit login cannot replace a corrupt credential file

If an existing Codex credential file is empty or contains truncated JSON, saving a successful new login returns "invalid existing credential JSON" even when ReplaceCredentials is true. The validation runs before the explicit-replacement branch, so reauthentication cannot repair the file without manual deletion.

cleanupTmp := true
defer func() {
if cleanupTmp {
_ = os.Remove(tmpPath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Low · Check deferred credential-temp cleanup errors

After an unsuccessful atomic write, a failing os.Remove silently leaves the credential temporary file behind. AGENTS.md requires checking and logging deferred errors; assigning the removal error to the blank identifier violates that mandate.

Comment on lines +195 to +197
merged[key] = value
} else {
delete(merged, key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical · Preserve refreshes from the model-fetch command

When cmd/fetch_codex_models.ensureAccessToken refreshes an expired credential, it updates Auth.Metadata and calls FileTokenStore.Save without a CodexRefreshPatch. That save reaches this merge, which replaces the newly rotated tokens with the old disk credentials and returns success. The new refresh token is consequently discarded while the persisted refresh token has already been consumed, causing subsequent refreshes to fail. The existing refresh writer needs to participate in the new versioned-patch contract.

// ToolSearch reports whether every credential behind a Home route can take
// Codex tool_search as Anthropic native tool search. Home computes it per
// route; nil means unknown and is treated as unsupported.
ToolSearch *bool `json:"tool_search,omitempty"`

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 · ToolSearch escapes registry clone isolation

The new ToolSearch pointer is shallow-copied by cloneModelInfo, which deep-copies only WebSearch. After RegisterClient, changing the original flag or a flag returned by GetModelsForClient also changes registry-owned metadata without a lock, publication, or generation update. This reproduces with a registered ToolSearch=true model and an output snapshot changed to false.

@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-12 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