Repository navigation
fix(native-chat): start a Codex chat without waiting on the background model probe - #23831
Conversation
…d model probe Opening a Codex chat kicks a session-less model-catalog probe (a throwaway read-only `codex app-server`) for the account, and the new chat's own options read then joined that probe's in-flight refresh through the catalog store's single-flight. When the probe's Codex hung, chat start waited out the probe's 15s deadline and failed with "codex app-server session exceeded 15000ms", even though the chat's own app-server was up. Single-flight now joins only a refresh by the same kind of lister. A live session lists over the connection it already holds; the probe still records its own failure in the store, and the picker keeps serving whichever listing succeeded.
…s listing Single-flight in the model catalog store was split by lister kind, so a new chat's acquire-time options read no longer joined the session-less probe, but it still joined any other live chat's in-flight listing for the same account. When that other chat's Codex was wedged or closing, the new chat waited out that chat's request timeout and failed to start with its error. Key in-flight listings by the lister's identity instead: a live session by its own connection, the probe by the probe itself. A lister still joins its own in-flight listing, and shouldRefresh still holds back a probe while any listing for the account is in flight.
…e every listing settles shouldRefresh reads 'any listing in flight for this account' from whether the per-account in-flight map exists, so that map must be dropped exactly when its last listing settles. Nothing covered this: removing the cleanup, or dropping the map on the first settle, passed every catalog test. A leftover map would stop every later background refresh and probe for the account.
A live session is keyed by its per-spawn catalog handle (minted in the same acquire as its connection), the probe by itself.
|
Status: ready for review. Head What this fixes: opening a Codex chat starts a background model-list probe. The chat's own startup joined that probe's in-flight request, so when the probe's Codex hung, the chat failed with "Chat could not be started" after 15 s. This was reproduced 3 of 3 times on Review (3 loops):
Verification:
Not verified:
Out of scope, possible follow-up: chat start still depends on the chat's own Not merged; marking ready for a human to decide. |
|
| (success) => { | ||
| this.refreshes.delete(fingerprint) | ||
| settle() | ||
| return this.recordSuccess(fingerprint, agent, success) |
There was a problem hiding this comment.
Older listing replaces newer models
When a slow probe and a chat list the same account at once, the chat can save a newer model list before the probe finishes. If the probe then succeeds with an older list, this call replaces the chat’s result and may save the older list to disk. The replacement is marked fresh, so the picker can show outdated models for up to ten minutes and validation can temporarily reject a newly available model. Prevent an older concurrent listing from replacing a newer one.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe catalog store tracks refreshes by fingerprint and lister identity. Calls with the same fingerprint and lister share an in-flight request. Calls with different listers can proceed independently. The Codex session-options and agent-model catalog service callers now pass the corresponding lister identity. Tests cover concurrent reads and refreshes, hanging listings, and refresh availability after listings settle. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to No actionable merge-blocking risk remains from the reviewed catalog coordination changes. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change prevents stalled background work from blocking unrelated chat startup while preserving the inspected account-isolation controls. No new credential access or privilege expansion was identified in those paths, but broader security coverage remains incomplete. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Description checkExplanation The description is detailed and covers the change, reason, user impact, testing, visual proof, review findings, and compatibility considerations. However, the required Linked Issue section has no issue link, and the
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: a128a721-8e39-48a2-8320-d140784031ef
📒 Files selected for processing (5)
src/main/codex/codex-structured-session-options-catalog.test.tssrc/main/codex/codex-structured-session-options.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-service.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-store.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.
| const listers: InFlightListings = this.refreshes.get(fingerprint) ?? new Map() | ||
| const inFlight = listers.get(lister) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
sed -n '1,270p' src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts
sed -n '80,155p' src/main/native-chat/agent-model-catalog/agent-model-catalog-store.test.ts
sed -n '85,135p' src/main/native-chat/agent-model-catalog/agent-model-catalog-service.tsRepository: stablyai/orca
Length of output: 13936
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- relevant files ---'
git ls-files 'src/main/native-chat/agent-model-catalog/*'
printf '%s\n' '--- store callers and symbols ---'
rg -n -C 4 'recordSuccess|\.refresh\(|refresh\(' src/main/native-chat/agent-model-catalog src/main/native-chat | head -n 260
printf '%s\n' '--- persistence implementation ---'
fd -i 'agent-model-catalog-persistence' src
for f in $(fd -i 'agent-model-catalog-persistence' src); do
echo "--- $f ---"
sed -n '1,240p' "$f"
done
printf '%s\n' '--- session listing references ---'
rg -n -C 5 'listModels|AgentModelCatalog|model catalog|catalog' src/main/native-chat | head -n 360
printf '%s\n' '--- PR diff summary and focused diff ---'
git diff --stat 4da485cac1c308243fc22f5ee9c9c15058eee7ea 40716436f78bd079820ca59f8ad028a177abe4e0
git diff --unified=35 4da485cac1c308243fc22f5ee9c9c15058eee7ea 40716436f78bd079820ca59f8ad028a177abe4e0 -- src/main/native-chat/agent-model-catalog/Repository: stablyai/orca
Length of output: 40398
🤖 get_repo_knowledge executed:
get_repo_knowledge stablyai/orca /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions
Length of output: 29698
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- persistence ---'
cat -n src/main/native-chat/agent-model-catalog/agent-model-catalog-persistence.ts
printf '%s\n' '--- exact refresh call sites ---'
rg -n -C 12 'store\.refresh|\.refresh\(fingerprint|recordSuccess\(' src/main --glob '*.ts' --glob '*.tsx'
printf '%s\n' '--- catalog listing APIs and session wiring ---'
rg -n -C 10 'agentModelCatalog|AgentModelCatalog|listModels|list.*model|models.*list|catalog' src/main/native-chat src/main/codex --glob '*.ts' --glob '*.tsx' | head -n 500
printf '%s\n' '--- relevant test definitions after omitted range ---'
sed -n '180,290p' src/main/native-chat/agent-model-catalog/agent-model-catalog-store.test.ts
sed -n '1,190p' src/main/native-chat/agent-model-catalog/agent-model-catalog-service.test.tsRepository: stablyai/orca
Length of output: 43312
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- Codex catalog/session path ---'
rg -n -C 20 'catalogAccess|store\.refresh|refresh\(|recordSuccess|listModels|list.*model|model.*list' src/main/codex/codex-structured-session-options.ts src/main/codex/codex-structured-session-options-read.ts src/main/codex --glob '*.ts' | head -n 360
printf '%s\n' '--- Claude live write-through path ---'
sed -n '220,330p' src/main/claude/claude-structured-session-options.ts
rg -n -C 12 'writeClaudeCatalogThrough|catalogAccess|recordSuccess|list.*model|model.*list' src/main/claude/claude-structured-session-options.ts src/main/claude --glob '*.ts' | head -n 300
printf '%s\n' '--- probe definition and service refresh ---'
rg -n -C 20 'probes|AgentModelCatalogProbe|create.*Probe|probe.*model|listModels' src/main src/shared --glob '*.ts' | head -n 360Repository: stablyai/orca
Length of output: 41924
Prevent an older listing from replacing a newer catalog entry.
When two listings for the same fingerprint overlap, the listing that starts first can finish last. recordSuccess then replaces the newer live-session result in memory and persistence. This can leave picker and validation reads with an older model catalog.
Assign a monotonic per-fingerprint start sequence to every async listing, including direct live-session writes. Commit a result only when its sequence is not older than the latest committed success. If an older result settles, return the current entry without persisting the stale result.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — full PR at 40716436f78 (5 files): the Codex chat-start hang caused by joining the background model-catalog probe's in-flight listing.
- In-flight listings keyed by lister identity —
AgentModelCatalogStore.refreshnow takes anAgentModelCatalogListerand keeps a per-fingerprint map of in-flight listings, so a chat's own option read never joins the probe's or another chat's listing and can no longer inherit their deadline or error. - New lister union —
AgentModelCatalogSessionAccess(the per-spawn handle stored onsession.catalogAccess) for a live chat; the probe function for the session-less probe. Call sites incodex-structured-session-options.tsandagent-model-catalog-service.tspass their own identity; a lister still shares its own concurrent reads (a chat's picker read and its option validation list once). - Account-level coordination preserved —
shouldRefreshstill consultsrefreshes.has(fingerprint), so a picker read cannot kick a probe while any listing for the account runs. - Settle cleanup —
settle()removes the lister and drops the fingerprint record only when the map empties, identity-guarded against a recreated map. - Tests — new cases pin hang isolation from a probe and from another chat, lister stability across a chat's own concurrent reads, and account release once every lister settles. I confirmed the three changed/adjacent suites pass (
vitest34 tests) and that the hang cases plausibly time out under the pre-change single-flight behavior.
I considered the out-of-order overwrite risk (a listing that starts first can finish last and replace a newer entry): overlapping listers list the same account through the same invocation resolver, so the only observable difference is origin/fetchedAt, and withKnownDefaultEfforts preserves a live child's defaults in either order. No sequence guard is warranted, so I did not flag it.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…-chat-start-probe # Conflicts: # src/main/native-chat/agent-model-catalog/agent-model-catalog-service.ts
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
dce7b0b8-65e5-497c-88e1-9943bd46e573
📒 Files selected for processing (5)
src/main/codex/codex-structured-session-options-catalog.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-service.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-service.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-store.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.
| /** For a reader with no Codex of its own: the first listing running for the account that | ||
| * succeeds, or null once all of them fail. Null at once when none is running. */ | ||
| pendingListing(fingerprint: string): Promise<AgentModelCatalogEntry | null> | null { | ||
| const runs = [...(this.refreshes.get(fingerprint)?.values() ?? [])] | ||
| if (runs.length === 0) { | ||
| return null | ||
| } | ||
| return new Promise((resolve) => { | ||
| let unsettled = runs.length | ||
| for (const run of runs) { | ||
| void run.then((entry) => { | ||
| if (entry) { | ||
| resolve(entry) | ||
| } else if (--unsettled === 0) { | ||
| resolve(null) | ||
| } | ||
| }) | ||
| } | ||
| }) | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '175,250p' src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts
sed -n '100,160p' src/main/native-chat/agent-model-catalog/agent-model-catalog-service.ts
sed -n '120,150p' src/main/codex/codex-structured-session-options-catalog.test.tsRepository: stablyai/orca
Length of output: 5713
🏁 Script executed:
printf '%s\n' '--- store ---'
nl -ba src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts | sed -n '1,265p'
printf '%s\n' '--- service ---'
nl -ba src/main/native-chat/agent-model-catalog/agent-model-catalog-service.ts | sed -n '1,220p'
printf '%s\n' '--- session options symbols ---'
rg -n -F -- 'readLiveCodexSessionOptions' src/main
rg -n -F -- 'waitForListing' src/main
printf '%s\n' '--- catalog-related tests ---'
rg -n -F -- 'pendingListing' src/main/native-chat src/main/codex || test "$?" -eq 1
rg -n -F -- 'waitForListing' src/main/native-chat src/main/codex || test "$?" -eq 1Repository: stablyai/orca
Length of output: 24632
🏁 Script executed:
printf '%s\n' '--- store tests ---'
nl -ba src/main/native-chat/agent-model-catalog/agent-model-catalog-store.test.ts | sed -n '105,155p'
printf '%s\n' '--- service tests ---'
nl -ba src/main/native-chat/agent-model-catalog/agent-model-catalog-service.test.ts | sed -n '185,295p'
printf '%s\n' '--- live session options implementation ---'
nl -ba src/main/codex/codex-structured-session-options.ts | sed -n '165,235p'
printf '%s\n' '--- session adapter method ---'
nl -ba src/main/codex/codex-structured-session-adapter.ts | sed -n '265,295p'
printf '%s\n' '--- RPC method ---'
rg -n -F -- 'waitForListing' src/main/runtime src/renderer src/shared || test "$?" -eq 1
printf '%s\n' '--- new-chat catalog test chronology ---'
nl -ba src/main/codex/codex-structured-session-options-catalog.test.ts | sed -n '130,155p'Repository: stablyai/orca
Length of output: 16100
🏁 Script executed:
printf '%s\n' '--- picker listing helper ---'
rg -n -F -- 'codexSessionCatalogListingForPicker' src/main/codex/codex-structured-session-options.ts
nl -ba src/main/codex/codex-structured-session-options.ts | sed -n '45,130p'
printf '%s\n' '--- shared result and wait contract ---'
nl -ba src/shared/agent-session-wire.ts | sed -n '425,445p'
nl -ba src/shared/rpc-contract/structured-agent-session-params.ts | sed -n '286,308p'
printf '%s\n' '--- renderer wait/retry flow ---'
nl -ba src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.ts | sed -n '45,125p'
printf '%s\n' '--- hook test asserting wait behavior ---'
nl -ba src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.test.tsx | sed -n '115,165p'Repository: stablyai/orca
Length of output: 11648
🏁 Script executed:
printf '%s\n' '--- PR diff for catalog store/service/tests ---'
git diff --unified=5 13d94acd4594f775ebcfdbe5a5dc38a0134a8f24 213ff14dd1184dfdfda1d219d6fcf504358c88c8 -- src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts src/main/native-chat/agent-model-catalog/agent-model-catalog-service.ts src/main/native-chat/agent-model-catalog/agent-model-catalog-store.test.ts src/main/native-chat/agent-model-catalog/agent-model-catalog-service.test.ts src/main/codex/codex-structured-session-options-catalog.test.ts
printf '%s\n' '--- service construction/probe bindings ---'
rg -n -F -- 'createAgentModelCatalogService' src/main
printf '%s\n' '--- probe definitions and timeout handling ---'
rg -n -i -- 'AgentModelCatalogProbe|probes:|modelCatalogProbe|catalogProbe|probe.*catalog' src/main/native-chat src/main/codexRepository: stablyai/orca
Length of output: 26283
🏁 Script executed:
printf '%s\n' '--- Codex catalog probe ---'
nl -ba src/main/codex/codex-model-catalog-probe.ts | sed -n '1,145p'
printf '%s\n' '--- production catalog service wiring ---'
nl -ba src/main/runtime/structured-agent-model-catalog-wiring.ts | sed -n '1,115p'
printf '%s\n' '--- probe creation and timeout argument bindings ---'
rg -n -F -- 'createCodexModelCatalogProbe' src/mainRepository: stablyai/orca
Length of output: 8826
🏁 Script executed:
rg -n -F -- 'function runCodexAppServerSession' src/main/codex
rg -n -F -- 'runCodexAppServerSession' src/main/codex/codex-app-server-session.ts
nl -ba src/main/codex/codex-app-server-session.ts | sed -n '1,240p'Repository: stablyai/orca
Length of output: 10628
🏁 Script executed:
nl -ba src/main/codex/codex-app-server-session.ts | sed -n '235,360p'Repository: stablyai/orca
Length of output: 3464
Resolve pending picker reads from later same-fingerprint successes.
pendingListing snapshots only the runs active when the read starts. If that snapshot contains a hung probe, a later chat refresh can store its successful catalog without resolving the picker’s wait. When the probe reaches its 15-second deadline, the service returns unknown without rereading the store, so the picker remains held until then and misses the available catalog. Resolve outstanding waits when a later listing for the same fingerprint succeeds.
There was a problem hiding this comment.
ℹ️ No critical issues — minor suggestions inline.
Reviewed changes — incremental re-review of the sync merge into main; the delta since the prior pullfrog review (40716436f78) is commit 213ff14dd1, which merges origin/main (13d94acd45) and reconciles it with the branch's lister-identity fix.
- Reconciled main's picker-wait feature with lister identity — the conflict in
agent-model-catalog-service.tswas resolved by routing a no-entry read throughpendingListing(fingerprint) ?? refresh(...), so a session-less read still joins a running listing now that the probe no longer shares a lister key with a live chat. - Added
AgentModelCatalogStore.pendingListing— resolves with the first in-flight listing for the account that succeeds, ornullonce all fail;nullimmediately when none is running. - Tests — pin that a
waitForListingread answers from a chat's already-running listing instead of starting a probe, and thatpendingListingresolves on first success (nullonce all snapshots fail).
I verified the merge-authored pendingListing mechanics (unsettled accounting cannot resolve null while a snapshotted run is pending; ?? falls through only on a real null) and that main's waitForListing / listingInProgress semantics survive the merge. All 42 tests in the three catalog suites pass.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
| ? (deps.store.pendingListing(fingerprint) ?? | ||
| deps.store.refresh(fingerprint, params.agent, probe, () => probe(home))) |
There was a problem hiding this comment.
pendingListing snapshots the in-flight listings at call time, so a waitForListing read that joined only the hung probe does not see the chat's own listing that starts a moment later. If that probe then fails, await listing resolves null and this returns unknown even though the chat's listing has already written an entry, so the picker can keep its fallback list until the next read.
Technical details
# A `waitForListing` read can miss a listing that starts after its snapshot
## Affected sites
- `agent-model-catalog-service.ts:124` — `pendingListing(fingerprint)` snapshots `refreshes.get(fingerprint)`; runs registered after the call are invisible to the returned promise.
- `agent-model-catalog-service.ts:134` — `entry = await listing` with no re-check of the store afterwards.
- `agent-model-catalog-store.ts:222` — `pendingListing` resolves on first success, or `null` once every snapshotted run fails.
- `codex-structured-session-options.ts:55` — the chat's listing runs under a distinct lister key, so it is not part of the probe's snapshot.
## Required outcome
- After `await listing` returns `null`, answer from `store.get(fingerprint)` if a concurrent listing has since landed, instead of returning `origin: 'unknown'`.
## Suggested approach
- `entry = await listing; entry ??= deps.store.get(fingerprint); if (!entry) { return { origin: 'unknown' } }`…-chat-start-probe
|
The reported Codex chat could open its session, then show “Chat could not be started” and Retry about 15 seconds later when a separate background model lookup hung. This PR lets a live chat use its own model lookup, so the reported Resolve with AI flow and a new Codex chat can start and answer while that background lookup remains stuck. The review found and fixed three problems in this PR: the model picker could miss a later successful lookup, a failed background lookup could hide an active chat lookup, and an older result could replace a newer model list. A cache eviction problem introduced during the review was also fixed. A rare error may still show the background lookup's failure detail instead of the chat's own detail. A picker that times out cannot update itself when models arrive later; that behavior predates this PR. The separate pre-existing case where the chat's own model listing fails during acquisition remains temporary here and is tracked by focused Draft follow-up #25754. Its possible 30-second cold saved-Fast first-send wait is an open product decision, so that follow-up remains Draft. On reviewed catalog commit |

ELI5
In a new Codex chat, the first message could appear and then fail with “Chat could not be started” and Retry after about 15 seconds. Orca had opened the chat’s Codex session, but it made that session wait for a separate background model lookup; when the background lookup hung, chat creation failed with it.
This change lets each chat ask its own Codex for its models. In the local hidden Electron reproduction, the old build failed after 16.1 seconds while the changed build replied. The reviewed catalog commit also replied in under a second in the “Resolve with AI” flow while the background lookup remained hung.
What Changed
A chat now shares a model lookup only with other reads from that same chat. A waiting model picker can use the first successful lookup for the same account, even if an earlier lookup is still running or failed. The shared catalog keeps the newer successful result when lookups finish out of order. A picker wait ends after 30 seconds if no catalog arrives.
The user sees a new Codex chat start and answer even when the background probe stalls. This affects the structured Codex chat on the execution host; terminal-backed chat is a separate path.
Why
The background lookup helps fill model choices before a chat exists. The chat already has its own Codex connection, so tying its start to the background process created the failure. Giving each live session its own lookup removes that dependency while preserving one lookup for simultaneous reads from that session. The bounded picker wait and ordering rule keep concurrent results useful without storing a second durable status flag.
Temporary difference: a separate, pre-existing case still fails if the chat’s own model lookup fails after Codex opens its thread. Fixing that requires changing acquisition-time option restoration, so it is in focused Draft follow-up #25754 from current main rather than expanding this PR. That Draft moves a cold saved-Fast lookup to the first send; its possible 30-second first-send wait remains an open product decision.
Linked Issue
The reported “Resolve with AI” failure in a rebase-conflict worktree and the related new-tab Codex start failure; no separate issue was filed. This also follows the Windows validation of #22971.
Visual Proof
Before, the old build showed a failed chat and Retry after the probe timeout:
At current head
37cadb17a72, local hidden Electron with a stub Codex showed the unresolved rebase conflict, a successful “Resolve with AI” reply, and a new Codex chat that sent and received “hello picker” while the background probe hung. The current-main merge did not change the PR's six-file catalog diff.Testing
Seven named stub-only test files: 94 tests passed on
cdc6810aa1a. They cover a hung probe, concurrent chats, failure TTL, picker waits, out-of-order writes, and cache eviction. After merging current main at37cadb17a72, eight named files passed 110 tests, including main's corrected Windows startup-line test.tc-gate pnpm tc:node, changed-lines code-quality gate, andgit diff --checkpassed; the lockfile is unchanged.Local hidden Electron before/after on earlier heads: main failed with Retry at +16.1 seconds; PR showed the stub reply at +2.1 seconds. On catalog commit
cdc6810aa1a, “Resolve with AI” visibly received its echo reply at +0.961 seconds with no failure across a 60-second observation. A new-tab Codex chat sent and received “hello picker” while a short-lived probe hung. Real Codex was never run in the rig.On exact head
37cadb17a72, local hidden Electron “Resolve with AI” visibly received the stub reply at +7.218 seconds with no failure or Retry during 60 seconds while the probe stayed hung. New tab → Codex opened a chat that sent and received “hello picker”; its stub emitted the reply +2.333 seconds after Send. The new-tab timing sampler did not save samples, so there is no precise visual timestamp for the picker closing or chat tab appearing.Remote M4Air and Windows high spec Electron QA were blocked by the session’s execution constraints, so there is no remote visual verdict. SSH sessions reject this local structured Codex path; no mobile client uses this catalog call. All current-head required CI checks passed; cross-version wire compatibility was skipped.
I tested these changes in a local hidden Electron rig on the final head.
Automated tests added.
Review
The review loop found and fixed three concurrency problems in this PR: a picker missing a later successful listing, a failed probe hiding another active listing, and an older result replacing a newer catalog. A cache-eviction problem introduced during the review fix was also corrected. A lower-impact error-detail attribution race remains deferred; a picker answer that times out cannot update itself if a catalog arrives later, which is also a limit of the existing one-shot read.
Agent skill upstream boundary
Notes
The catalog remains capped and its new waiter callbacks and order bookkeeping end when their work settles or the wait deadline expires. No wire, persisted-schema, dependency, or new-process change is in this diff. The branch is mergeable and the current-head review gates are complete.
Checklist