Skip to content

stack 6/7: triage the overnight PRs and fix the #955 defects they found - #973

Merged
lidge-jun merged 14 commits into
devfrom
codex/stack6-overnight-triage
Aug 4, 2026
Merged

stack 6/7: triage the overnight PRs and fix the #955 defects they found#973
lidge-jun merged 14 commits into
devfrom
codex/stack6-overnight-triage

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Stack

6/6 — overnight PR triage

Base: codex/915-cooldown-recovery-probe (#955)

Summary

Nine PRs arrived overnight while this stack was in review. This layer carries the three that belong here, and records an evidence-backed disposition for the other six.

PR Author Verdict
#967 @Yuxin-Qiao two real defects in my own #955 — carried
#965 @Yuxin-Qiao correct fix for #962 — carried
#968 @DevMello real independent defect — carried
#963 @MarcTCruz duplicate of #965, broader and weaker — closing
#966 @Yuxin-Qiao fifth #914 design, two falsifications survive — stays open
#964 @Yuxin-Qiao real, but its own review
#970 @stephen-drew real, out of scope here
#961 @Yuxin-Qiao feature, not a bug
#969 @Wibias CI governance, needs its own policy/security review

All carried commits keep their authors (cherry-pick -x, patch-id verified identical).

#967 found two real bugs in my #955, and both verify

Monthly-classified snapshots were rejected. My predicate picked the required window from the plan name; the parser picks it from the window duration. A Team account whose primary window is explicitly monthly parses to monthlyPercent only, so every successful fresh read was thrown away:

$ bun run .tmp/probe_967.ts   # before
parsed        = {"monthlyPercent":12,"monthlyResetAt":1900000000}
recoverable?  = false      <-- cooled forever

That is the same failure #915 exists to fix, reintroduced for monthly-window plans — the third time this predicate has been wrong in the same direction.

The probe's own token refresh looked like a replacement. getValidCodexToken() refreshes a near-expiry token mid-probe and advances the credential generation by one; my settle required an exact match, so a valid fresh reading was discarded and recovery waited another interval. Fenced on replacedAt, which is preserved by a CAS refresh and stamped fresh by a real replacement.

Where I disagreed with #967, and why

Its remedy for the first defect — accept whatever window the parser wrote — is too permissive in the other direction. A tertiary-only response also writes monthlyPercent, describes a different period, and says nothing about the weekly quota that actually gates the account. The two were literally indistinguishable:

tertiary-only    {"monthlyPercent":7,"monthlyResetAt":1900000000}   -> would recover
explicit-monthly {"monthlyPercent":12,"monthlyResetAt":1900000000}  -> should recover

So this adds provenance at the source instead of guessing at the sink: parseUsageQuota() records monthlyIsPrimaryWindow when the value came from an explicitly-monthly primary window, and recovery requires it before accepting monthly-only evidence for a weekly-quota plan. Go/Free are unaffected — the monthly window governs them either way.

The flag is propagated through every copy site (setAccountQuotaFromParsed, updateAccountQuota, the credits-only and weekly-preserving branches, parseUpstreamQuotaHeaders) and pinned by tests, because a flag that silently fails to persist makes the guard decorative.

Why #963 loses to #965

Both claim #962. #962 is about a custom row replacing a same-slug provider row, and #965 models exactly that — it inherits from the row actually being replaced, so it also retains live /models metadata. #963 recomputes config hints for every custom row including unmatched ones, cannot retain discovered metadata, and rewrites tests/catalog-vision-sidecar-modalities.test.ts from "no registry reasoning leaks onto an unmatched override" to expecting that leak while dropping three fetch should not be called guards. Changing a test that encodes a deliberate prior decision, to make a broader change pass, is what decided it.

Why #966 stays open

It is a genuine advance on the four previously-falsified #914 designs — real Bun labels, no hostname probing, manual redirects on the pool paths. But two falsifications survive, both reproduced live: fetchWithTransientRetry() discards a prior 503 when a later attempt rejects, so a real account failure is recorded as neutral; and the five sidecar paths it newly classifies still use default-follow fetch, so a credential-visible 307 to a dead host reads as neutral after the origin has already seen the Authorization header. It does supersede #922.

Verification

Nine pre-existing toEqual assertions needed updating for the new field — values unchanged, and each now states which side of the provenance distinction it is on.

Audit

Four rounds, three FAIL. The reviewer caught the tertiary-only over-permissiveness, then two further copy sites of the same class. Every finding was reproduced at runtime before fixing.

Summary by CodeRabbit

  • New Features
    • Improved Google/Gemini tool-choice handling, including required, disabled, automatic, forced, and selectively allowed tools.
    • Custom model entries now inherit missing capability details from provider information while preserving explicit settings.
  • Bug Fixes
    • Improved quota recovery accuracy by distinguishing verified monthly limits from supplementary data.
    • Quota recovery now handles eligible credential refreshes without unnecessarily failing.
    • Claude integrations on Google services now correctly manage tool declarations and validated tool-calling mode.
  • Tests
    • Added coverage for tool selection, model capabilities, quota provenance, and credential refresh scenarios.

lidge-jun and others added 12 commits August 4, 2026 09:33
#967 found two real defects in my own #955 code and both verify at runtime: a
Team account with a monthly window could never recover because the predicate
picked its window by plan name while the parser picks by window duration, and
the probe's own token refresh was mistaken for an external credential
replacement.

#963 and #965 both claim #962; #965 wins because it inherits from the row it
actually replaces rather than recomputing config hints, and because #963
rewrites an existing regression contract to justify a broader change.

#966 is a fifth design for #914 that survives two of the four prior
falsifications but not all: mixed 5xx-then-rejection still loses the
attributable failure, and five newly-classified sidecar paths keep default
redirects, so a credential-visible 307 to a dead host still reads as neutral.
…ned refresh generations

Addresses the two unresolved Codex review threads on #955:

- isCompleteCodexQuotaRecoverySnapshot() required weeklyPercent for every
  non-Go/Free plan by plan name, but the parser classifies windows by
  duration: a Team response with an explicitly monthly primary window
  parses to monthlyPercent only, so those accounts could never recover
  early and stayed cooled until their predicted expiry.
- settleCodexQuotaRecoveryProbe() required the claim-time credential
  generation to match exactly. A probe-owned token refresh inside
  getValidCodexToken() advances the generation by one before WHAM
  completes, so a successful fresh reading was rejected and the account
  waited another probe interval. replacedAt is preserved by refresh and
  stamped by external replacement, so it fences the +1 transition.

(cherry picked from commit 79d2164)
… recovers a weekly plan

#967 correctly found that requiring weeklyPercent by plan name stranded Team
accounts whose primary window is explicitly monthly. Its remedy — accept any
window the parser wrote — was too permissive in the other direction: a
tertiary-only response also writes monthlyPercent, describes a different
period, and says nothing about the weekly quota that actually gates the
account, so it could clear a cooldown on a reading of the wrong window.

parseUsageQuota() now records monthlyIsPrimaryWindow when the monthly value
came from an explicitly-monthly PRIMARY window, and recovery requires that
provenance before accepting monthly-only evidence for a weekly-quota plan.
Go/Free are unaffected: the monthly window governs them either way.

The two shapes were previously indistinguishable — both parsed to
{monthlyPercent} with no way to tell which window produced it.
Recovery reads freshQuota directly, so this is not on its path today — but
setAccountQuotaFromParsed() copies fields one by one, and a cached snapshot
that kept monthlyPercent while dropping monthlyIsPrimaryWindow would look
exactly like tertiary-only data to any future reader. A flag that silently
fails to persist makes the guard decorative, and that failure would be
invisible rather than loud.
…er parser too

The audit noted the cache round trip was untested — so the guard could have
been silently reduced to decoration by a later refactor. The test now asserts
the flag survives setAccountQuotaFromParsed(), and ablating that copy fails it.

parseUpstreamQuotaHeaders() recognizes the same explicitly-monthly primary
window and now records the same provenance. It is not on the recovery path
today, but two parsers disagreeing about what a bare monthlyPercent means is
the kind of divergence that surfaces later as an unexplainable bug.
…enance

Nine tests asserted parseUsageQuota()/getAccountQuota() output with toEqual, so
the new monthlyIsPrimaryWindow field failed them on shape while every value was
unchanged. Each expectation now states which side of the distinction it is on,
which is the thing those tests were already about:

- explicit-monthly PRIMARY windows carry the flag
- the Go/Free thirtyDayOnly branch does not (recovery never consults it there)
- a tertiary-sourced monthly value does not, which is the case that made the
  guard necessary
- a credits-only refresh preserving prior usage does not

The cached monthly-A snapshot now carries it too, proving propagation through
setAccountQuotaFromParsed() rather than only asserting the parse.
The last copy site of the same class: an unrelated weekly update rebuilt the
record and carried monthlyPercent forward without its provenance, silently
downgrading a proven explicit-primary reading to unproven. The mirror case
matters as much — a caller-supplied monthly value arrives with no window
information, so it must REPLACE the proof rather than inherit it.

Both directions are now pinned, and ablating the carry fails the test.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change records overnight pull-request triage decisions and implements four fixes: Gemini tool-choice serialization, provider catalog metadata inheritance, quota provenance tracking, and credential-refresh-aware quota recovery.

Changes

Google tool-choice handling

Layer / File(s) Summary
Gemini and Antigravity tool-choice serialization
src/adapters/google.ts, tests/google-adapter.test.ts
tool_choice values now map to Gemini functionCallingConfig. The adapter omits configuration without tools and removes tools for Claude-on-Antigravity when the choice is none. Tests cover mappings, filtering, and VALIDATED behavior.

Provider catalog enrichment

Layer / File(s) Summary
Custom catalog metadata inheritance
src/codex/catalog/provider-fetch.ts, tests/codex-catalog.test.ts
Custom rows inherit missing capability fields from provider-derived rows with the same routed slug. Explicit custom fields remain authoritative. The regression test covers reasoning and tool-call metadata.

Quota recovery and credential fencing

Layer / File(s) Summary
Quota provenance and recovery evidence
src/codex/quota.ts, tests/codex-cooldown-recovery.test.ts, tests/codex-routing.test.ts, tests/rate-limit-reset-credits.test.ts
Quota state records monthlyIsPrimaryWindow. Parsing and update paths preserve or clear the flag. Weekly-classified recovery accepts monthly evidence only when the flag is proven.
Credential-refresh probe settlement
src/codex/routing.ts, tests/codex-cooldown-recovery.test.ts
Recovery claims carry credential replacement timestamps. Settlement accepts the original generation or one unchanged-lineage generation advance when the new credential remains live.

Overnight triage record

Layer / File(s) Summary
Triage decisions and layer 6 carry list
devlog/_plan/260804_overnight_triage/000_dispositions.md
The record classifies nine pull requests, documents two defects, selects the preferred duplicate fix, rejects unresolved changes, and lists the layer 6 carry item.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant GoogleAdapter
  participant GeminiAPI
  Caller->>GoogleAdapter: Build request with tools and tool_choice
  GoogleAdapter->>GoogleAdapter: Map tool_choice to functionCallingConfig
  GoogleAdapter->>GeminiAPI: Send tools and optional toolConfig
  GeminiAPI-->>Caller: Return configured model response
Loading

Possibly related PRs

Suggested labels: bug

Suggested reviewers: wibias, ingwannu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's main work: triaging overnight PRs and fixing the identified #955 defects.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/stack6-overnight-triage

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Comment @coderabbitai help to get the list of available commands.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Stack navigation

  1. stack 1/6: triage the open issue surface and lock the bug plan #951 — triage the open issue surface and lock the bug plan (base dev)
  2. stack 2/7: price long-context requests at the published long rate (#908) #952 — long-context pricing tiers, Cost estimates ignore published long-context pricing tiers (OpenAI >272k, xAI >=200k) #908 (base stack 1/6: triage the open issue surface and lock the bug plan #951)
  3. stack 3/7: carry six contributor bug fixes with authorship intact #953 — carry six contributor bug fixes (base stack 2/7: price long-context requests at the published long rate (#908) #952)
  4. stack 4/7: keep an explicit thinking disable through translation (#545) #954 — Claude Desktop classifier thinking round-trip, Claude Desktop 3P Auto Mode classifier retries after 64-token Anthropic OAuth outputs #545 (base stack 3/7: carry six contributor bug fixes with authorship intact #953)
  5. stack 5/7: probe reset-derived cooldowns without waiting to be selected (#915) #955 — cooldown early-recovery probe, [Bug]: Reset-derived cooldowns can miss early recovery while another pool account remains eligible #915 (base stack 4/7: keep an explicit thinking disable through translation (#545) #954)
  6. stack 6/7: triage the overnight PRs and fix the #955 defects they found #973 — overnight PR triage + the stack 5/7: probe reset-derived cooldowns without waiting to be selected (#915) #955 defects it surfaced (base stack 5/7: probe reset-derived cooldowns without waiting to be selected (#915) #955)

Review and merge bottom-up. Each PR targets the preceding stack branch, so its Files changed view contains only that layer.

The layers touch largely disjoint files, so any layer can be retargeted to dev and taken independently. The one real dependency is #973 on #955 — it fixes defects in #955's own code, so those two should land together or in that order.

#954 needs human security review per MAINTAINERS.md (Anthropic OAuth request construction). It is deliberately below #955/#973 so the first three can land without waiting on it.

Carried with authorship preserved: #939, #942, #943, #944, #945, #948 in #953; #965, #967, #968 in #973.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74c4f765e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +113 to +117
**Falsification 3 survives on five expanded surfaces.** Manual redirects were
added only to Responses and Compact; the five sidecar paths #966 newly
classifies still use default-follow fetch, so a credential-bearing sidecar that
receives a 307 to a dead host is misclassified as neutral — after the origin
already read the `Authorization` header:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move unfixed security triage out of the devlog

This tracked _plan note publishes the exact credential-bearing redirect counterexample and live reproduction while the document later states that #914 remains open and #966 is not mergeable. Pushing this commit would disclose bypass reasoning and reproduction details before the fix ships, and deleting them later would not remove them from Git history; keep this material in .tmp/ or mktemp scratch space and publish only the fixed outcome.

AGENTS.md reference: AGENTS.md:L61-L69

Useful? React with 👍 / 👎.

const enrichedByName = new Map(activeProviders);
// Provider-derived rows keyed by their Codex-facing slug: a custom override replaces the row
// with the same slug below, so that row's provider capability metadata is the inheritance source.
const replacedByRoutedSlug = new Map(all.map(model => [routedSlug(model.provider, model.id), model]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Key inherited metadata by native model identity

When a provider exposes both a slash-containing ID such as foo/bar and the literal ID foo-bar, routedSlug() maps both to the same key. This map therefore retains whichever row appears last, so a custom override for the exact foo-bar model can inherit the context, modalities, or reasoning metadata of foo/bar, even though routing gives the literal native ID precedence. Index the inheritance source by provider plus raw model ID, or explicitly reject ambiguous encoded-slug collisions.

Useful? React with 👍 / 👎.

@lidge-jun lidge-jun changed the title stack 6/6: triage the overnight PRs and fix the #955 defects they found stack 6/7: triage the overnight PRs and fix the #955 defects they found Aug 4, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

Stack navigation — 7 layers, review and merge bottom-up

Layer PR Contents
1/7 #951 merged af3ddedb4 — 22 label corrections + the plan unit
2/7 #952 long-context pricing tiers (#908)
3/7 #953 six carried contributor bug fixes, authorship intact
4/7 #954 explicit thinking disable through translation (#545)
5/7 #955 cooldown early-recovery probe (#915)
6/7 #973 overnight PR triage + fixes to #955's own defects
7/7 #980 NIM vision classification (#956), service repair (#970), qwen3.8-max rename

Each layer targets the branch below it, so its diff only makes sense on that base — enforce-target skips the wrong-base gate for stacked children by design (AGENTS.md, Branch policy). Review bottom-up; a layer cannot merge before its parent lands.

Note for the merge sequence: retargeting a child after its parent merges emits an edited event, which ci.yml does not listen for. A green check on the same head sha therefore proves nothing about the new merge base — merge current dev into the child to force a synchronize run before merging it.

@lidge-jun
lidge-jun changed the base branch from codex/915-cooldown-recovery-probe to dev August 4, 2026 04:43

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@devlog/_plan/260804_overnight_triage/000_dispositions.md`:
- Line 133: Update the adapter description in the disposition entry for `#968` to
capitalize “Google” in the phrase “Google adapter,” leaving the surrounding text
unchanged.
- Around line 79-80: Escape the leading PR-number references in the affected
paragraphs, including `#962`, `#965`, `#963`, and `#966`, by escaping the hash or using
inline code formatting so markdownlint MD018 is resolved while the rendered text
remains unchanged.

In `@src/codex/routing.ts`:
- Around line 522-536: Replace the timestamp-based credentialReplacedAt lineage
marker with a persistent opaque ID or monotonic external-replacement version,
and update the generation fence around isCodexAccountGenerationLive to compare
that collision-free marker. Preserve the marker in
saveCodexAccountCredentialIfGeneration and carry it through src/codex/routing.ts
ranges 142-143, 450, 479, and 504-506. Add a fixed-clock test in
tests/codex-cooldown-recovery.test.ts ranges 171-197 proving same-millisecond
external replacement retains the cooldown, while keeping the probe-owned refresh
test and asserting /oauth/token is called, generation advances once, and the
marker is unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 661d7d1f-f467-4035-8922-901af918c4e0

📥 Commits

Reviewing files that changed from the base of the PR and between eb2ceb2 and dbe935a.

📒 Files selected for processing (10)
  • devlog/_plan/260804_overnight_triage/000_dispositions.md
  • src/adapters/google.ts
  • src/codex/catalog/provider-fetch.ts
  • src/codex/quota.ts
  • src/codex/routing.ts
  • tests/codex-catalog.test.ts
  • tests/codex-cooldown-recovery.test.ts
  • tests/codex-routing.test.ts
  • tests/google-adapter.test.ts
  • tests/rate-limit-reset-credits.test.ts

Comment on lines +79 to +80
#962 is specifically about a custom row *replacing* a same-slug provider row.
#965 models exactly that: it indexes the rows deduplication will replace and

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Escape PR-number references at paragraph starts.

Lines [79]-[80], [84], [86], and [98] begin with # followed immediately by digits. markdownlint reports MD018 for these lines. Escape the hash or use inline code formatting so the rendered text remains #962, #965, #963, and #966.

Proposed fix
-#962 is specifically about a custom row
+\`#962` is specifically about a custom row
-#965 models exactly that
+\`#965` models exactly that
-#963 instead recomputes `catalogHintsFromProviderConfig()`
+\`#963` instead recomputes `catalogHintsFromProviderConfig()`
-#962 requires.
+\`#962` requires.
-#966 targets `#914`
+\`#966` targets `#914`

Also applies to: 84-86, 98-98

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 79-79: No space after hash on atx style heading

(MD018, no-missing-space-atx)


[warning] 80-80: No space after hash on atx style heading

(MD018, no-missing-space-atx)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@devlog/_plan/260804_overnight_triage/000_dispositions.md` around lines 79 -
80, Escape the leading PR-number references in the affected paragraphs,
including `#962`, `#965`, `#963`, and `#966`, by escaping the hash or using inline code
formatting so markdownlint MD018 is resolved while the rendered text remains
unchanged.

Source: Linters/SAST tools


## The rest

**#968** (@DevMello) — the google adapter dropped `tool_choice` entirely: `none`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Capitalize Google.

Write Google adapter at Line [133]. Google is a proper name, and static analysis flags the lowercase form.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~133-~133: “Google” is a proper noun and needs to be capitalized.
Context: ...## The rest #968 (@DevMello) — the google adapter dropped tool_choice entirely:...

(A_GOOGLE)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@devlog/_plan/260804_overnight_triage/000_dispositions.md` at line 133, Update
the adapter description in the disposition entry for `#968` to capitalize “Google”
in the phrase “Google adapter,” leaving the surrounding text unchanged.

Source: Linters/SAST tools

Comment thread src/codex/routing.ts
Comment on lines +522 to +536
const currentRecord = readCodexAccountRecord(claim.accountId);
const proofGeneration = proof.credentialGeneration;
// A probe-owned token refresh (getValidCodexToken) advances the credential generation by
// exactly one while preserving `replacedAt`; an external credential replacement bumps the
// generation too but stamps a fresh `replacedAt`. Accept the +1 transition only when the
// claim-time lineage is intact AND the generation the fresh quota was proven under is live.
const generationFenced = proofGeneration !== undefined
&& (proofGeneration === claim.credentialGeneration
? isCodexAccountGenerationLive(claim.accountId, proofGeneration)
: proofGeneration === claim.credentialGeneration + 1
&& currentRecord?.replacedAt === claim.credentialReplacedAt
&& isCodexAccountGenerationLive(claim.accountId, proofGeneration));
const fenced = (health.cooldownGeneration ?? 0) === claim.cooldownGeneration
&& (health.probeLeaseGeneration ?? 0) === claim.cooldownGeneration
&& claim.credentialGeneration === proof.credentialGeneration
&& isCodexAccountGenerationLive(claim.accountId, claim.credentialGeneration);
&& generationFenced;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- routing symbols and relevant sections ---'
rg -n -C 8 'credentialReplacedAt|replacedAt|credentialGeneration|generationFenced|saveCodexAccountCredential|isCodexAccountGenerationLive' src/codex/routing.ts src tests/codex-cooldown-recovery.test.ts
printf '%s\n' '--- credential record definitions and writes ---'
rg -n -C 8 'interface .*Codex|type .*Codex|replacedAt|credentialGeneration|saveCodexAccountCredential' src tests

Repository: lidge-jun/opencodex

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- routing.ts targeted ranges ---'
sed -n '120,155p;430,545p' src/codex/routing.ts
printf '%s\n' '--- account-store declarations and writes ---'
rg -n -C 12 'replacedAt|generation|function saveCodexAccountCredential|const saveCodexAccountCredential|saveCodexAccountCredentialIfGeneration|readCodexAccountRecord' src/codex/account-store.ts
printf '%s\n' '--- cooldown recovery tests around the supplied regression ---'
sed -n '130,225p' tests/codex-cooldown-recovery.test.ts

Repository: lidge-jun/opencodex

Length of output: 26134


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- recovery probe proof flow ---'
rg -n -C 14 'runCodexCooldownRecoveryProbes|getValidCodexToken|credentialGeneration|settleCodexQuotaRecoveryProbe|probe.*proof|proof:' src/codex/routing.ts
printf '%s\n' '--- account record type ---'
sed -n '1,45p' src/codex/account-store.ts
printf '%s\n' '--- all relevant replacement-marker tests ---'
rg -n -C 8 'replacedAt|credentialReplacedAt|external replacement|replacement.*cooldown|cooldown.*replacement' tests/codex* src/codex
printf '%s\n' '--- deterministic settlement model ---'
python3 - <<'PY'
from dataclasses import dataclass

`@dataclass`
class Claim:
    generation: int
    replaced_at: int | None

`@dataclass`
class Record:
    generation: int
    replaced_at: int | None

def generation_fenced(claim: Claim, record: Record, proof_generation: int) -> bool:
    live = record.generation == proof_generation
    return (
        proof_generation == claim.generation and live
    ) or (
        proof_generation == claim.generation + 1
        and record.replaced_at == claim.replaced_at
        and live
    )

# Probe-owned refresh: generation advances, lineage marker is preserved.
claim = Claim(7, 1_700_000_000_000)
refresh = Record(8, 1_700_000_000_000)
assert generation_fenced(claim, refresh, 8)

# External replacement in the same millisecond: generation advances, but
# Date.now() produces the same marker, so the current predicate also accepts it.
same_ms_replacement = Record(8, 1_700_000_000_000)
assert generation_fenced(claim, same_ms_replacement, 8)

# A replacement in a different millisecond is rejected by the marker check.
different_ms_replacement = Record(8, 1_700_000_000_001)
assert not generation_fenced(claim, different_ms_replacement, 8)

print("probe-owned refresh accepted: yes")
print("same-millisecond external replacement accepted: yes")
print("different-millisecond external replacement rejected: yes")
PY

Repository: lidge-jun/opencodex

Length of output: 21223


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- existing replacement race test ---'
sed -n '255,305p' tests/codex-cooldown-recovery.test.ts
printf '%s\n' '--- recovery runner locations and implementation ---'
rg -l 'runCodexCooldownRecoveryProbes' src tests
rg -n -C 18 'runCodexCooldownRecoveryProbes|settleCodexQuotaRecoveryProbe\(' src tests
printf '%s\n' '--- deterministic settlement model ---'
python3 - <<'PY'
from dataclasses import dataclass

`@dataclass`
class Claim:
    generation: int
    replaced_at: object

`@dataclass`
class Record:
    generation: int
    replaced_at: object

def generation_fenced(claim, record, proof_generation):
    live = record.generation == proof_generation
    return (
        (proof_generation == claim.generation and live)
        or (
            proof_generation == claim.generation + 1
            and record.replaced_at == claim.replaced_at
            and live
        )
    )

claim = Claim(7, 1700000000000)

# The intended probe-owned refresh preserves the marker.
refresh = Record(8, 1700000000000)
assert generation_fenced(claim, refresh, 8)

# An external replacement in the same millisecond receives the same Date.now()
# marker and passes the current +1 predicate.
same_ms_replacement = Record(8, 1700000000000)
assert generation_fenced(claim, same_ms_replacement, 8)

# A replacement in another millisecond is rejected by the current marker check.
different_ms_replacement = Record(8, 1700000000001)
assert not generation_fenced(claim, different_ms_replacement, 8)

print("probe-owned refresh accepted: yes")
print("same-millisecond external replacement accepted: yes")
print("different-millisecond external replacement rejected: yes")
PY

Repository: lidge-jun/opencodex

Length of output: 40119


Use a collision-free credential-lineage marker.

saveCodexAccountCredential() assigns replacedAt with Date.now(). If an external replacement occurs in the same millisecond as the claim-time marker, generation N + 1 and the same replacedAt pass the +1 branch at src/codex/routing.ts:522-536. The probe can then clear a cooldown using quota from an external credential.

  • Replace credentialReplacedAt with a persistent opaque lineage ID or monotonic external-replacement version.
  • Preserve the marker during saveCodexAccountCredentialIfGeneration().
  • Carry it through src/codex/routing.ts:142-143, 450, 479, and 504-506.
  • Add a fixed-clock test in tests/codex-cooldown-recovery.test.ts that retains the cooldown after same-millisecond replacement.
  • Keep the probe-owned refresh test. Assert that /oauth/token is called, generation advances once, and the lineage marker is unchanged.
📍 Affects 2 files
  • src/codex/routing.ts#L522-L536 (this comment)
  • src/codex/routing.ts#L142-L143
  • src/codex/routing.ts#L450-L450
  • src/codex/routing.ts#L479-L479
  • src/codex/routing.ts#L504-L506
  • tests/codex-cooldown-recovery.test.ts#L171-L197
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/routing.ts` around lines 522 - 536, Replace the timestamp-based
credentialReplacedAt lineage marker with a persistent opaque ID or monotonic
external-replacement version, and update the generation fence around
isCodexAccountGenerationLive to compare that collision-free marker. Preserve the
marker in saveCodexAccountCredentialIfGeneration and carry it through
src/codex/routing.ts ranges 142-143, 450, 479, and 504-506. Add a fixed-clock
test in tests/codex-cooldown-recovery.test.ts ranges 171-197 proving
same-millisecond external replacement retains the cooldown, while keeping the
probe-owned refresh test and asserting /oauth/token is called, generation
advances once, and the marker is unchanged.

Source: Path instructions

@lidge-jun
lidge-jun merged commit 880d2e6 into dev Aug 4, 2026
22 checks passed
chrisae9 pushed a commit to chrisae9/opencodex that referenced this pull request Aug 4, 2026
…ice repair

Two overnight contributor PRs describe real defects the lidge-jun#951-lidge-jun#973 stack does
not touch. This unit plans layer 7 as their reconstruction.

lidge-jun#964 cannot be carried: five ids in its hand-written text-only list are
natively image-capable per NVIDIA's own docs (inkling, minimax-m3, kimi-k2.6,
step-3.7-flash, mistral-medium-3.5-128b). A false positive there is silent —
the model can read the image, but the proxy substitutes another model's text
description. Issue lidge-jun#956's own body carries two of the same errors, so reporter
and author shared the premise. 010 inverts the design: maintain the 15 verified
vision-capable ids and derive text-only as the complement, so an unclassified
new model defaults to sidecar-on rather than to the bug being fixed.

lidge-jun#970's premise is right but its diff is oversized: repairService() and
'ocx service repair' already exist here. 020 records the safety proof that
matters — repair throws when not installed and the update path runs after
'ocx stop', but stop never deregisters on any of the three platforms. It also
closes a hole lidge-jun#970 leaves: bin/ocx.mjs infers service presence from a
possibly-stale marker, where repair would throw and lose the managed service.

030 sequences the bottom-up merge and issue closure, including the lidge-jun#954
security-review gate that can legitimately stop the queue.
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.

3 participants