Skip to content

fix(desktop): keep server CORS headers so preflights cache - #47560

Merged
Hona merged 1 commit into
anomalyco:v2from
Hona:cors-preflight-cache
Sep 6, 2026
Merged

fix(desktop): keep server CORS headers so preflights cache#47560
Hona merged 1 commit into
anomalyco:v2from
Hona:cors-preflight-cache

Conversation

@Hona

@Hona Hona commented Sep 6, 2026

Copy link
Copy Markdown
Member

Every Desktop API call was preceded by a fresh CORS preflight. In a 310 s netlog from a Desktop debug export, 1519 requests to the local server were 759 real calls + 759 OPTIONS, and Chromium's preflight cache logged hit-and-fail 591 times and hit-and-pass never. Those preflights spent 20.9 s on the critical path (mean 27.5 ms each, 79 over 50 ms) and doubled the server's request load during every session-tab mount.

Why the cache never hit

sequenceDiagram
  participant R as renderer (oc://renderer)
  participant M as main: onHeadersReceived
  participant C as Chromium CORS
  participant S as server
  R->>S: OPTIONS /api/…  (Access-Control-Request-Headers: authorization)
  S-->>M: access-control-allow-headers: authorization<br/>access-control-max-age: 86400
  M-->>C: access-control-allow-headers: *   ← overwritten
  Note over C: cache entry stored as "*"
  R->>C: GET /api/… (Authorization: Basic …)
  Note over C: PreflightCache always checks with<br/>NonWildcardRequestHeadersSupport(true):<br/>"*" does not cover Authorization → hit-and-fail → erase
  C->>S: OPTIONS again …
Loading

wireRendererHeaders ran addRendererHeaders on every response and it did upsertHeader(headers, "Access-Control-Allow-Headers", ["*"]). Per the Fetch spec * never matches Authorization, and Chromium's PreflightCache::CheckIfRequestCanSkipPreflight validates cached entries strictly, so the entry was discarded on every lookup while the fresh preflight (checked leniently) kept passing and re-caching *.

Change

packages/desktop/src/main/windows/headers.ts (pure, testable; no electron import):

export function addRendererHeaders(headers: object, options: { document: boolean }) {
  upsertHeader(headers, "Access-Control-Allow-Origin", ["*"])
  if (!hasHeader(headers, "Access-Control-Allow-Headers")) {
    upsertHeader(headers, "Access-Control-Allow-Headers", ["*, authorization"])
  }
  if (!hasHeader(headers, "Access-Control-Max-Age")) {
    upsertHeader(headers, "Access-Control-Max-Age", ["7200"])
  }
  if (options.document) upsertHeader(headers, documentPolicyHeader, [jsCallStacksDocumentPolicy])
}
  • A server's own Access-Control-Allow-Headers is kept. The OpenCode server echoes the requested header list with max-age: 86400, so Chromium now caches each (origin, URL, method) preflight for its 2 h cap.
  • Servers that send no CORS headers (the reason fix(desktop-electron): add CORS headers to main window webRequest #23633 added this hook) still get permissive values, but the fallback names authorization explicitly so those cache too, and gets a Max-Age (Chromium's default without one is 5 s).
  • Removed the onBeforeSendHeaders listener that added Access-Control-Allow-Origin: * to request headers. It is a response header, so it did nothing, but a blocking listener costs a main-process round trip per request.

Expected effect on the trace above: ~759 preflights → one per distinct URL, and the client's 4-slot request queue (#47441) stops spending half its slots on OPTIONS.

@Hona
Hona requested a review from Brendonovich as a code owner September 6, 2026 03:20
Copilot AI lite review requested due to automatic review settings September 6, 2026 03:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Hona
Hona enabled auto-merge (squash) September 6, 2026 03:21
@Hona
Hona merged commit 1cf576a into anomalyco:v2 Sep 6, 2026
10 checks passed
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.

2 participants