Skip to content

[codex] preserve failed local development#1

Closed
Cmochance wants to merge 1 commit into
mainfrom
codex/preserve-failed-local-dev
Closed

[codex] preserve failed local development#1
Cmochance wants to merge 1 commit into
mainfrom
codex/preserve-failed-local-dev

Conversation

@Cmochance
Copy link
Copy Markdown
Owner

This draft PR preserves the current local working state from a failed development pass so the main branch is not polluted.

It is intentionally not ready for merge. The follow-up work should inspect, split, or discard parts of this diff before any normal review.

Current contents captured in this branch:

  • Windows-side model mapping and provider gateway changes from the interrupted/failed workstream.
  • Shared frontend/runtime wiring touched by that workstream.
  • Small temporary Kimi gateway/streaming verification directories that were present locally and may help reconstruct what happened.

Validation status:

  • No fresh validation was run for this preservation PR.
  • A pre-push sensitive-value scan did not find API key/token-looking values in the staged temporary profile files; matches found were source-code field names only.

@Cmochance
Copy link
Copy Markdown
Owner Author

Closing per maintainer request to prevent accidental merge into main.

@Cmochance Cmochance closed this May 6, 2026
Cmochance added a commit that referenced this pull request May 10, 2026
Three review agents flagged a mix of MUST / HIGH / SHOULD items on
the perf bundle. This commit addresses every blocker and the test
gaps; nice-to-have items (B8 in-flight dedupe via Notify, corrupt-
profile.json logging) are left for the broader follow-up backlog.

Errors no longer silently fall through:

- `try_refresh_via_chatgpt_api` (mac + win) now matches on the
  inner `AppError`. Re-login signatures (`token_invalidated`,
  `refresh_token_reused`, etc.) propagate as `AUTH_REFRESH_RELOGIN_REQUIRED`
  immediately instead of falling through to the app-server RPC
  fallback that would hit the same dead refresh_token. Other
  errors `eprintln!` and fall through. Predicate is hoisted to
  `chatgpt_api::looks_like_relogin_required` so the existing
  matcher in `codex_app_server::map_rpc_error` can share it next
  time it's touched.
- Bulk plan refresh's four `let _ =` swallows on the per-profile
  API call, both `sync_profile_*` writes, and the post-loop index
  reload all gain `eprintln!` lines so a systematic failure across
  every profile is visible in stderr instead of silently leaving
  the dashboard stale.
- Front-end `applySnapshot` failure after a per-card Refresh logs
  via `console.warn`. Toast suppression matches pre-PR-3
  `refreshAllData(false)` UX (the user already saw the success
  toast and a follow-up failure isn't actionable), but the warning
  makes a systematic failure debuggable.

Active-card live-quota regression fix (code-reviewer Should #1):

- B4 used to skip `getCurrentLiveQuota` unconditionally after a
  per-card Refresh. That worked for non-active cards but lost the
  "JSONL session count newer than API value" merge for the active
  card. Now we re-fetch live quota only when the refreshed profile
  is the active one — saves the JSONL pass on non-active refreshes
  while preserving the pre-PR active-panel behavior.

Threshold deduplication:

- The `6 * 60 * 60 * 1000` literal lived in three places (mac
  refresh_runtime, win refresh_runtime, dashboard.rs bulk gate).
  Hoist to `chatgpt_api::PLAN_FRESHNESS_TTL_MS` so a future tuning
  change moves all three sites in lock-step.

OnceLock no longer poisons the HTTP client:

- B6's cache shape changed from `OnceLock<Result<Client, String>>`
  to `OnceLock<Client>`. A first-call build failure no longer
  permanently caches the error; subsequent calls re-attempt
  the build.

New tests:

- `should_force_oauth_rotation` mac + win each get four cases:
  missing `last_plan_check_ms`, recent (rotation skipped), stale
  (rotation forced), and future-dated (clock-skew defense:
  rotation skipped, documenting the deliberate "trust local cache"
  stance).

96 tests passing on macOS (was 92, +4).

Cache invariant comment added to `profiles_index::store_cached_index`
documenting the rule every per-profile mutation site already
follows: any caller mutating per-profile metadata must call
`load_profiles_index` afterwards so the 250 ms cache repopulates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cmochance added a commit that referenced this pull request May 10, 2026
* perf(chatgpt_api): share a single reqwest::Client across calls

Every `refresh_profile_via_api*` invocation used to call
`build_http_client()` which spun up a fresh blocking reqwest Client,
and therefore a fresh TLS connection pool, a fresh handshake state,
and a fresh resolver cache. Bulk refresh paid that cost N times in a
row; single-card Refresh paid it once per click.

`reqwest::blocking::Client` already wraps an `Arc<Inner>`, so handing
clones from a process-wide `OnceLock<Result<Client, _>>` is cheap and
lets the underlying connection pool / TLS state stick around between
calls. Build error is captured into the cell once and surfaced as
`HTTP_CLIENT_BUILD_FAILED` to every caller, matching the prior
behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* perf(refresh): drop force_token_rotation on Refresh button repeat clicks

`try_refresh_via_chatgpt_api` was hardcoded to
`force_token_rotation: true` so the id_token claims (plan tier,
subscription expiry) would move within a single click. Reasonable
when the user clicked Refresh once after a plan change, but every
repeat click in the same session paid the full OAuth POST + usage GET
round-trip even though the cached plan info was certainly fresh.

Gate the forced rotation on `last_plan_check_ms`: rotate when the
cached check is older than 6 hours (or absent); otherwise reuse the
cached `id_token` claims and let `chatgpt_api`'s inner `access_token_expired`
predicate drive rotation only when the access_token itself is about
to expire.

The "user clicked Refresh and expects fresh plan info" guarantee
still holds — once per day per profile the heavier path runs. What
changes is that a frantic-click session now costs 1 × OAuth POST
across N clicks instead of N × OAuth POST. Mac and Windows mirrors
get the same change pending the planned mac/win refresh_runtime
merge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* perf(dashboard): gate bulk plan refresh on last_plan_check_ms

`refresh_all_oauth_profile_plans_silent` runs at app launch and on
each local-day rollover. It used to walk every OAuth profile
unconditionally with `force_token_rotation: true`, which on a
5-account workspace meant 10–25 s of background OAuth + usage round-
trips trickling cards updating one-by-one — visible to the user as
plan / quota fields changing under their cursor for tens of seconds
after each launch.

Skip any profile whose `last_plan_check_ms` is younger than 6 hours.
Sized to be longer than the 5-min silent dashboard ticker (active
profile keeps moving) but shorter than the day-rollover trigger so a
paid-tier change on a parked profile still surfaces by the next day.
Repeat launches inside the same working day now cost zero round-
trips.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* perf(profiles_index): 250ms in-process result cache for back-to-back IPC pairs

The front-end's `refreshAllData` issues `get_profiles_snapshot` and
`get_current_live_quota` concurrently via `Promise.all`. Each Tauri
command independently calls `load_profiles_index`, so the disk-side
reconcile + atomic save of `profiles.json` happened twice in
near-lockstep — wasted I/O, wasted index-rewrite, occasional
needless mtime-bumping on every dashboard refresh.

Cache the rebuilt index in process for 250 ms keyed by `codex_home`.
Sized to dedupe the back-to-back pair (which fire microseconds apart)
without holding stale data across user-visible state changes — every
action handler that mutates profiles already calls
`load_profiles_index` at the end of its operation and refreshes the
cache as a side-effect, so the next IPC sees the latest data.

Tests bypass the cache via `cfg(test)` so per-test fs setup remains
observable on the next call without explicit invalidation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* perf(refresh): drop redundant refreshAllData after per-card Refresh

`drainRefreshQueue` used to call `refreshAllData(false)` after every
successful per-card Refresh, which fans out to two IPCs:
`getProfilesSnapshot` (rebuilds + saves the profiles index again
even though `refresh_profile` just did) and `getCurrentLiveQuota`
(walks the JSONL parse cache for data that's already live in the
index we just refreshed).

The backend's `refresh_profile` already wrote the new quota + plan
into `profiles.json` before returning. The front-end just needs to
re-read the snapshot to pick those up — `getCurrentLiveQuota`'s only
job here is hitting the JSONL cache for a value the backend already
authoritatively persisted.

Replace the call with a single `getProfilesSnapshot` + `applySnapshot`.
Failures are best-effort (transient snapshot fetch failures leave
the cards on their pre-refresh state, no worse than swallowing the
failure entirely). Saves one IPC round-trip and one JSONL cache pass
per Refresh click; combined with the `profiles_index` 250 ms cache
this also dedupes the index reconcile on the snapshot side.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review: address PR-3 audit findings

Three review agents flagged a mix of MUST / HIGH / SHOULD items on
the perf bundle. This commit addresses every blocker and the test
gaps; nice-to-have items (B8 in-flight dedupe via Notify, corrupt-
profile.json logging) are left for the broader follow-up backlog.

Errors no longer silently fall through:

- `try_refresh_via_chatgpt_api` (mac + win) now matches on the
  inner `AppError`. Re-login signatures (`token_invalidated`,
  `refresh_token_reused`, etc.) propagate as `AUTH_REFRESH_RELOGIN_REQUIRED`
  immediately instead of falling through to the app-server RPC
  fallback that would hit the same dead refresh_token. Other
  errors `eprintln!` and fall through. Predicate is hoisted to
  `chatgpt_api::looks_like_relogin_required` so the existing
  matcher in `codex_app_server::map_rpc_error` can share it next
  time it's touched.
- Bulk plan refresh's four `let _ =` swallows on the per-profile
  API call, both `sync_profile_*` writes, and the post-loop index
  reload all gain `eprintln!` lines so a systematic failure across
  every profile is visible in stderr instead of silently leaving
  the dashboard stale.
- Front-end `applySnapshot` failure after a per-card Refresh logs
  via `console.warn`. Toast suppression matches pre-PR-3
  `refreshAllData(false)` UX (the user already saw the success
  toast and a follow-up failure isn't actionable), but the warning
  makes a systematic failure debuggable.

Active-card live-quota regression fix (code-reviewer Should #1):

- B4 used to skip `getCurrentLiveQuota` unconditionally after a
  per-card Refresh. That worked for non-active cards but lost the
  "JSONL session count newer than API value" merge for the active
  card. Now we re-fetch live quota only when the refreshed profile
  is the active one — saves the JSONL pass on non-active refreshes
  while preserving the pre-PR active-panel behavior.

Threshold deduplication:

- The `6 * 60 * 60 * 1000` literal lived in three places (mac
  refresh_runtime, win refresh_runtime, dashboard.rs bulk gate).
  Hoist to `chatgpt_api::PLAN_FRESHNESS_TTL_MS` so a future tuning
  change moves all three sites in lock-step.

OnceLock no longer poisons the HTTP client:

- B6's cache shape changed from `OnceLock<Result<Client, String>>`
  to `OnceLock<Client>`. A first-call build failure no longer
  permanently caches the error; subsequent calls re-attempt
  the build.

New tests:

- `should_force_oauth_rotation` mac + win each get four cases:
  missing `last_plan_check_ms`, recent (rotation skipped), stale
  (rotation forced), and future-dated (clock-skew defense:
  rotation skipped, documenting the deliberate "trust local cache"
  stance).

96 tests passing on macOS (was 92, +4).

Cache invariant comment added to `profiles_index::store_cached_index`
documenting the rule every per-profile mutation site already
follows: any caller mutating per-profile metadata must call
`load_profiles_index` afterwards so the 250 ms cache repopulates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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