Skip to content

refactor(codex): keyed turn routing, client-scoped rate limits, and resume subscription safety#101376

Merged
steipete merged 6 commits into
mainfrom
claude/cranky-volhard-0eb701
Jul 7, 2026
Merged

refactor(codex): keyed turn routing, client-scoped rate limits, and resume subscription safety#101376
steipete merged 6 commits into
mainfrom
claude/cranky-volhard-0eb701

Conversation

@steipete

@steipete steipete commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Closes #101338
Related: #93313

What Problem This Solves

Fixes an issue where a Codex usage-limit failure could report another account's rate-limit reset time and block the wrong auth profile: the rate-limit cache was process-global, so snapshots observed by one physical app-server client leaked into usage-limit errors on a different client. It also replaces the broad per-attempt notification/request fanout that made every attempt see and re-filter all app-server traffic, let thread/resume adopt mismatched or indeterminate subscriptions on pooled clients, and reinstalled auth-refresh handlers per attempt/side-question instead of per client.

Why This Change Was Made

Re-implements the turn-routing and client-runtime-ownership track of #93313 on current main (that branch is thousands of commits behind and cannot be rebased). One turn router per physical client owns explicit per-thread routes with a reserve → arm → bind lifecycle: startup reserves the route before thread/resume so early notifications buffer instead of racing, turn/start arms the route, and binding the accepted turn id flushes buffered traffic in wire order. Rate limits move to a WeakMap keyed by the physical client with per-limitId revisions, merging rolling account/rateLimits/updated payloads per the protocol's sparse-update contract; usage-limit errors only trust a snapshot for profile blocking when the same client observed a primary update during the failing turn's startup. thread/resume moves into a dedicated module that retires the exact client when acceptance is indeterminate (only a structured RPC rejection proves no subscription). The positional-arg client-factory.ts indirection is deleted. Out of scope, tracked in #101338: the #93313 native-subagent-monitor status-revision rework and its compact generalization (main has since shipped a newer compaction completion-watch design).

User Impact

Usage-limit errors now report the reset time for the account that actually hit the limit, and auth-profile blocking can no longer be triggered by another client's rate-limit traffic. Turn traffic on shared Codex app-server processes is routed per thread/turn, and a resume that Codex did not provably accept can no longer return a subscribed client to the shared pool. No config or API surface changes.

Evidence

  • Codex protocol contracts verified against the vendored @openai/codex 0.142.5 generated schemas and upstream codex-rs source (sparse rolling rate-limit updates, willRetry camelCase, top-level threadId on turn-scoped notifications, thread/unsubscribe status semantics).
  • Full extensions/codex suite green on Blacksmith Testbox (95 files / 1985 tests; final rerun in PR checks).
  • pnpm check:changed green on Blacksmith Testbox (typecheck extensions + tests, oxlint, guards): Actions run https://github.com/openclaw/openclaw/actions/runs/28841817056.
  • Codex autoreview (gpt-5.5) ran to clean over three cycles; accepted findings (resume fallback gating, idle-clock rewind clamp, will_retry alias removal) are fixed with regression tests. New regression coverage: cross-limit rate-limit trust, resume echo/mismatch retirement, rejected-resume unsubscribe fallback, keyed-router unit suite (940-line turn-router.test.ts).

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7695a70370

ℹ️ 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 28 to +30
export function readCodexNotificationThreadId(record: JsonObject): string | undefined {
return readNestedTurnThreadId(record) ?? readString(record, "threadId");
const thread = isJsonObject(record.thread) ? record.thread : undefined;
return readString(record, "threadId") ?? (thread ? readString(thread, "id") : undefined);

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.

P1 Badge Preserve routing for {turn} terminal notifications

The current Codex app-server README documents turn/started and turn/completed notifications as params shaped like { turn } rather than carrying a top-level threadId, and this change also stops reading turn.threadId. With that wire shape readScope() has no thread id, so ClientTurnRouter.routeNotification() returns before terminal handling; normal turns then ignore turn/completed and wait until the idle/attempt timeout. Please keep a nested or otherwise keyed correlation path for {turn} events before dropping them.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 7, 2026, 2:21 AM ET / 06:21 UTC.

Summary
The branch refactors Codex app-server runtime ownership with per-client rate-limit/auth observers, keyed thread/turn routing, safer resume subscription handling, and updated tests while removing the positional client-factory helper.

PR surface: Source +803, Tests +1148. Total +1951 across 48 files.

Reproducibility: yes. at source level: current main has the global Codex rate-limit cache and direct resume adoption described by the linked issue, and the PR source shows the active-turn wait regression. I did not run a live multi-client Codex reproduction in this read-only review.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: persistent cache schema: extensions/codex/src/app-server/client-runtime.test.ts, persistent cache schema: extensions/codex/src/app-server/client-runtime.ts, persistent cache schema: extensions/codex/src/app-server/event-projector.test.ts, persistent cache schema: extensions/codex/src/app-server/event-projector.ts, persistent cache schema: extensions/codex/src/app-server/rate-limit-cache.test.ts, persistent cache schema: extensions/codex/src/app-server/run-attempt-test-harness.ts, and 7 more. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #101338
Summary: This PR is the active implementation candidate for the open canonical Codex app-server rate-limit/routing/resume-safety issue; the older broad design PR is closed unmerged and only a source reference.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🦪 silver shellfish
Patch quality: 🦪 silver shellfish
Result: blocked until real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Restore exact activeTurnIds matching for resumed native-turn waits.
  • [P1] Add redacted live Codex app-server proof for rate-limit isolation, resume retirement, and normal routed turn completion.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body cites tests, CI, and autoreview but does not include a redacted live Codex app-server run, terminal output, logs, or transcript showing the changed multi-client rate-limit, resume, or turn-routing behavior after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] The PR currently lets any terminal notification on a resumed thread satisfy the active-native-turn wait, which can allow turn/start while one of the reported active turns is still running.
  • [P2] The PR body and live comments still lack redacted live Codex app-server proof for multi-client rate-limit isolation, resume mismatch/timeout retirement, and normal routed turn completion.
  • [P1] This is a broad session/auth/shared-client refactor; mistakes can misroute turn traffic, hang shared-client runs, retire the wrong client, or block the wrong auth profile even with green CI.

Maintainer options:

  1. Fix exact native-turn waiting and add live proof (recommended)
    Restore waiting for the specific resumed activeTurnIds, rerun focused Codex validation, and attach redacted live proof before merge.
  2. Accept the broad runtime risk explicitly
    Maintainers may decide to land after tests despite limited live proof, but they would own possible session routing, auth-profile, or stall regressions.
  3. Split or pause this branch
    If the combined refactor remains too risky, close or pause this branch in favor of smaller PRs that prove one runtime invariant at a time.

Next step before merge

  • [P1] Manual review is needed because the PR has a protected maintainer label, missing real behavior proof, broad session/auth merge risk, and a concrete active-turn routing blocker.

Maintainer decision needed

  • Question: After the active-turn wait regression is fixed and live proof is added, should maintainers land this broad Codex app-server ownership refactor as the fix for Codex app-server: keyed turn routing, client-scoped rate limits, and resume subscription safety #101338 or split it into smaller PRs?
  • Rationale: The direction matches the canonical issue, but the change spans session routing, auth-profile rate-limit attribution, and shared-client availability; maintainer risk tolerance is needed after the concrete blocker is repaired.
  • Likely owner: steipete — He is the strongest history owner for this Codex app-server track and the linked follow-up direction.
  • Options:
    • Fix, prove, then land this PR (recommended): Restore exact active-turn waiting, attach redacted live Codex proof, and use this branch as the single fix path for the canonical issue.
    • Split the refactor: Separate rate-limit scoping, turn routing, and resume retirement if maintainers want lower blast radius and proof per piece.
    • Pause until stronger live evidence: Keep the branch open but do not merge until real Codex app-server logs or transcripts demonstrate the changed behavior on current head.

Security
Cleared: The diff is security-sensitive because it touches auth and shared-client routing, but I found no concrete credential, authorization, sandbox, dependency, or supply-chain regression.

Review findings

  • [P2] Wait for the exact resumed native turns — extensions/codex/src/app-server/run-attempt.ts:2809
Review details

Best possible solution:

Restore exact activeTurnIds-based native-turn waiting, add redacted current-head live Codex app-server proof for rate-limit, resume, and routing paths, then land this PR only if maintainer review accepts the broad refactor.

Do we have a high-confidence way to reproduce the issue?

Yes at source level: current main has the global Codex rate-limit cache and direct resume adoption described by the linked issue, and the PR source shows the active-turn wait regression. I did not run a live multi-client Codex reproduction in this read-only review.

Is this the best way to solve the issue?

No for the current head: per-client runtime state and keyed routing are the right layer, but the active native-turn wait must preserve exact reported turn IDs before this is the best fix. Live behavior proof is still required before merge.

Full review comments:

  • [P2] Wait for the exact resumed native turns — extensions/codex/src/app-server/run-attempt.ts:2809
    Late finding: this was also visible in the earlier reviewed head. Current main waits for the specific activeTurnIds returned by resume, but this PR now calls the route-level wait with no IDs; that helper resolves on any terminal notification for the thread. If multiple native turns are in progress, or a stale completion arrives first, OpenClaw can send turn/start while one reported active turn is still running and reintroduce the active-turn collision this wait is meant to prevent.
    Confidence: 0.88
    Late finding: first raised on code an earlier review cycle already covered.

Overall correctness: patch is incorrect
Overall confidence: 0.86

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 3175d8e7423d.

Label changes

Label changes:

  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦪 silver shellfish and patch quality is 🦪 silver shellfish.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🧂 unranked krab, so this older rating label is no longer current.

Label justifications:

  • P1: The PR targets a Codex app-server bug that can affect real turns and the wrong auth profile, and current head still has a merge-blocking session-routing regression.
  • merge-risk: 🚨 session-state: The diff changes thread/turn routing, buffering, resume adoption, active native-turn waiting, and shared-client route release behavior.
  • merge-risk: 🚨 auth-provider: The diff changes auth-refresh handler ownership and the rate-limit snapshots used to decide auth-profile blocking.
  • merge-risk: 🚨 availability: The new route lifecycle waits, buffers, retires, and releases shared clients, so a routing bug can hang or stall Codex turns.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦪 silver shellfish and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body cites tests, CI, and autoreview but does not include a redacted live Codex app-server run, terminal output, logs, or transcript showing the changed multi-client rate-limit, resume, or turn-routing behavior after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +803, Tests +1148. Total +1951 across 48 files.

View PR surface stats
Area Files Added Removed Net
Source 25 1507 704 +803
Tests 23 1650 502 +1148
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 48 3157 1206 +1951

What I checked:

Likely related people:

  • steipete: He has recent merged history on the Codex app-server surface, authored the related Codex 0.142 floor work that left this router/client-runtime follow-up, and owns the current implementation direction beyond only opening this PR. (role: feature-history owner and likely follow-up owner; confidence: high; commits: e5798e547770, 3b65e2302a55, 7a5e5e1852ad; files: extensions/codex/src/app-server/run-attempt.ts, extensions/codex/src/app-server/thread-lifecycle.ts, extensions/codex/src/app-server/shared-client.ts)
  • vincentkoc: Current history shows release and Codex app-server stabilization work that carried the existing app-server behavior into the latest release branch. (role: recent adjacent area contributor; confidence: medium; commits: e085fa1a3ffd; files: extensions/codex/src/app-server/rate-limit-cache.ts, extensions/codex/src/app-server/run-attempt.ts, extensions/codex/src/app-server/thread-lifecycle.ts)
  • cxbAsDev: Current blame on the active native-turn wait and rate-limit cache points to this recent main commit, which is useful routing evidence but not ownership of the proposed PR. (role: recent current-main committer; confidence: low; commits: 08349608f291; files: extensions/codex/src/app-server/run-attempt.ts, extensions/codex/src/app-server/rate-limit-cache.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (1 earlier review cycle)
  • reviewed 2026-07-07T05:30:28.431Z sha 7695a70 :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 7, 2026
steipete added 6 commits July 7, 2026 06:53
Replace the process-global rate-limit cache with a WeakMap keyed by the
physical app-server client, tracking per-limitId revisions. Rolling
account/rateLimits/updated notifications merge sparsely per the protocol
contract (credits/individualLimit/planType survive nulls), and
usage-limit errors only trust a snapshot for auth-profile blocking when
the same client observed a primary update during the failing turn's
startup. Fixes cross-client rate-limit bleed in usage-limit error
messages. A new client-runtime module installs the
account/chatgptAuthTokens/refresh handler and the rate-limit observer
once per physical client, replacing per-start inline handlers in
shared-client, run-attempt, and side-question.
…resume

Move the thread/resume request out of thread-lifecycle into a dedicated
thread-resume module that retires the exact physical client when resume
acceptance is indeterminate: only a structured RPC rejection proves
Codex holds no subscription, so any other failure abandons the client
instead of returning it to the shared pool. Resume responses naming a
different thread now fail closed (assertCodexThreadResumeSubscription),
and the fresh-thread fallback requires a released subscription unless
the resume was a proven RPC rejection.
…ent factory

Delete the lazy positional-argument CodexAppServerClientFactory and use
the options-object factory type exported from shared-client. Callers in
run-attempt, compact, bounded-turn, provider-capabilities, and the
web-search provider now default to getLeasedSharedCodexAppServerClient
directly; the lazy indirection was ineffective because those modules
already import shared-client statically.
Install one turn router per physical app-server client and replace the
broad per-attempt notification/request fanout with explicit per-thread
routes. Attempt startup reserves the thread route (before thread/resume
on the resume path, so early notifications buffer instead of racing),
run-attempt activates it with receive-time, queued, and request
handlers, arms the route before turn/start, binds the accepted turn id
to flush buffered traffic in wire order, and releases the route on
cleanup. Requests for a pending turn wait for binding instead of being
auto-declined, native turn completion waits use route state instead of
scanning buffered notifications, and correlation readers now match the
canonical v2 wire shapes only (top-level threadId, nested turn.id). The
unscoped response-delta lease-count attribution and its client API are
deleted along with the retired correlation predicates.
…tests

SQLite bindings are keyed by session identity rather than the per-test
temp dir, so earlier tests leaked resumable threads into fresh-start
expectations. The old silent resume-failure fallback masked the leak;
subscription safety surfaces it.
The loop reuses one session identity across iterations, so the previous
iteration's thread would resume against a harness that cannot serve it.
@steipete
steipete force-pushed the claude/cranky-volhard-0eb701 branch from 7695a70 to 22c295c Compare July 7, 2026 06:04

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22c295c9cb

ℹ️ 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".

},
});
const nativeTurnCompleted = await waitForActiveNativeTurnCompletion(activeNativeTurnIds);
const nativeTurnCompleted = await waitForActiveNativeTurnCompletion();

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.

P2 Badge Wait for the reported active turn IDs

When thread/resume reports active native turns, this call now waits for any terminal notification on the thread instead of the specific thread.lifecycle.activeTurnIds that blocked startup. If the resumed thread has multiple in-progress native turns, or a stale/other turn completion arrives while one listed active turn is still running, turn/start can be sent too early and reintroduce the active-turn collision this wait is meant to avoid. Use the reported active turn ids (or the exact watchNativeTurnCompletion path) before treating the native turn as complete.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 7, 2026
@steipete
steipete merged commit 554d772 into main Jul 7, 2026
222 of 224 checks passed
@steipete
steipete deleted the claude/cranky-volhard-0eb701 branch July 7, 2026 06:25
@steipete

steipete commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 7, 2026
…esume subscription safety (openclaw#101376)

* feat(codex): scope app-server rate limits to the physical client

Replace the process-global rate-limit cache with a WeakMap keyed by the
physical app-server client, tracking per-limitId revisions. Rolling
account/rateLimits/updated notifications merge sparsely per the protocol
contract (credits/individualLimit/planType survive nulls), and
usage-limit errors only trust a snapshot for auth-profile blocking when
the same client observed a primary update during the failing turn's
startup. Fixes cross-client rate-limit bleed in usage-limit error
messages. A new client-runtime module installs the
account/chatgptAuthTokens/refresh handler and the rate-limit observer
once per physical client, replacing per-start inline handlers in
shared-client, run-attempt, and side-question.

* refactor(codex): split thread/resume subscription safety into thread-resume

Move the thread/resume request out of thread-lifecycle into a dedicated
thread-resume module that retires the exact physical client when resume
acceptance is indeterminate: only a structured RPC rejection proves
Codex holds no subscription, so any other failure abandons the client
instead of returning it to the shared pool. Resume responses naming a
different thread now fail closed (assertCodexThreadResumeSubscription),
and the fresh-thread fallback requires a released subscription unless
the resume was a proven RPC rejection.

* refactor(codex): replace client-factory positional DI with shared-client factory

Delete the lazy positional-argument CodexAppServerClientFactory and use
the options-object factory type exported from shared-client. Callers in
run-attempt, compact, bounded-turn, provider-capabilities, and the
web-search provider now default to getLeasedSharedCodexAppServerClient
directly; the lazy indirection was ineffective because those modules
already import shared-client statically.

* feat(codex): route app-server turn traffic through a keyed turn router

Install one turn router per physical app-server client and replace the
broad per-attempt notification/request fanout with explicit per-thread
routes. Attempt startup reserves the thread route (before thread/resume
on the resume path, so early notifications buffer instead of racing),
run-attempt activates it with receive-time, queued, and request
handlers, arms the route before turn/start, binds the accepted turn id
to flush buffered traffic in wire order, and releases the route on
cleanup. Requests for a pending turn wait for binding instead of being
auto-declined, native turn completion waits use route state instead of
scanning buffered notifications, and correlation readers now match the
canonical v2 wire shapes only (top-level threadId, nested turn.id). The
unscoped response-delta lease-count attribution and its client API are
deleted along with the retired correlation predicates.

* test(codex): reset the shared binding store between thread-lifecycle tests

SQLite bindings are keyed by session identity rather than the per-test
temp dir, so earlier tests leaked resumable threads into fresh-start
expectations. The old silent resume-failure fallback masked the leak;
subscription safety surfaces it.

* test(codex): reset the binding store between delivery-hint iterations

The loop reuses one session identity across iterations, so the previous
iteration's thread would resume against a harness that cannot serve it.
sheyanmin pushed a commit to sheyanmin/openclaw that referenced this pull request Jul 8, 2026
…esume subscription safety (openclaw#101376)

* feat(codex): scope app-server rate limits to the physical client

Replace the process-global rate-limit cache with a WeakMap keyed by the
physical app-server client, tracking per-limitId revisions. Rolling
account/rateLimits/updated notifications merge sparsely per the protocol
contract (credits/individualLimit/planType survive nulls), and
usage-limit errors only trust a snapshot for auth-profile blocking when
the same client observed a primary update during the failing turn's
startup. Fixes cross-client rate-limit bleed in usage-limit error
messages. A new client-runtime module installs the
account/chatgptAuthTokens/refresh handler and the rate-limit observer
once per physical client, replacing per-start inline handlers in
shared-client, run-attempt, and side-question.

* refactor(codex): split thread/resume subscription safety into thread-resume

Move the thread/resume request out of thread-lifecycle into a dedicated
thread-resume module that retires the exact physical client when resume
acceptance is indeterminate: only a structured RPC rejection proves
Codex holds no subscription, so any other failure abandons the client
instead of returning it to the shared pool. Resume responses naming a
different thread now fail closed (assertCodexThreadResumeSubscription),
and the fresh-thread fallback requires a released subscription unless
the resume was a proven RPC rejection.

* refactor(codex): replace client-factory positional DI with shared-client factory

Delete the lazy positional-argument CodexAppServerClientFactory and use
the options-object factory type exported from shared-client. Callers in
run-attempt, compact, bounded-turn, provider-capabilities, and the
web-search provider now default to getLeasedSharedCodexAppServerClient
directly; the lazy indirection was ineffective because those modules
already import shared-client statically.

* feat(codex): route app-server turn traffic through a keyed turn router

Install one turn router per physical app-server client and replace the
broad per-attempt notification/request fanout with explicit per-thread
routes. Attempt startup reserves the thread route (before thread/resume
on the resume path, so early notifications buffer instead of racing),
run-attempt activates it with receive-time, queued, and request
handlers, arms the route before turn/start, binds the accepted turn id
to flush buffered traffic in wire order, and releases the route on
cleanup. Requests for a pending turn wait for binding instead of being
auto-declined, native turn completion waits use route state instead of
scanning buffered notifications, and correlation readers now match the
canonical v2 wire shapes only (top-level threadId, nested turn.id). The
unscoped response-delta lease-count attribution and its client API are
deleted along with the retired correlation predicates.

* test(codex): reset the shared binding store between thread-lifecycle tests

SQLite bindings are keyed by session identity rather than the per-test
temp dir, so earlier tests leaked resumable threads into fresh-start
expectations. The old silent resume-failure fallback masked the leak;
subscription safety surfaces it.

* test(codex): reset the binding store between delivery-hint iterations

The loop reuses one session identity across iterations, so the previous
iteration's thread would resume against a harness that cannot serve it.
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…esume subscription safety (openclaw#101376)

* feat(codex): scope app-server rate limits to the physical client

Replace the process-global rate-limit cache with a WeakMap keyed by the
physical app-server client, tracking per-limitId revisions. Rolling
account/rateLimits/updated notifications merge sparsely per the protocol
contract (credits/individualLimit/planType survive nulls), and
usage-limit errors only trust a snapshot for auth-profile blocking when
the same client observed a primary update during the failing turn's
startup. Fixes cross-client rate-limit bleed in usage-limit error
messages. A new client-runtime module installs the
account/chatgptAuthTokens/refresh handler and the rate-limit observer
once per physical client, replacing per-start inline handlers in
shared-client, run-attempt, and side-question.

* refactor(codex): split thread/resume subscription safety into thread-resume

Move the thread/resume request out of thread-lifecycle into a dedicated
thread-resume module that retires the exact physical client when resume
acceptance is indeterminate: only a structured RPC rejection proves
Codex holds no subscription, so any other failure abandons the client
instead of returning it to the shared pool. Resume responses naming a
different thread now fail closed (assertCodexThreadResumeSubscription),
and the fresh-thread fallback requires a released subscription unless
the resume was a proven RPC rejection.

* refactor(codex): replace client-factory positional DI with shared-client factory

Delete the lazy positional-argument CodexAppServerClientFactory and use
the options-object factory type exported from shared-client. Callers in
run-attempt, compact, bounded-turn, provider-capabilities, and the
web-search provider now default to getLeasedSharedCodexAppServerClient
directly; the lazy indirection was ineffective because those modules
already import shared-client statically.

* feat(codex): route app-server turn traffic through a keyed turn router

Install one turn router per physical app-server client and replace the
broad per-attempt notification/request fanout with explicit per-thread
routes. Attempt startup reserves the thread route (before thread/resume
on the resume path, so early notifications buffer instead of racing),
run-attempt activates it with receive-time, queued, and request
handlers, arms the route before turn/start, binds the accepted turn id
to flush buffered traffic in wire order, and releases the route on
cleanup. Requests for a pending turn wait for binding instead of being
auto-declined, native turn completion waits use route state instead of
scanning buffered notifications, and correlation readers now match the
canonical v2 wire shapes only (top-level threadId, nested turn.id). The
unscoped response-delta lease-count attribution and its client API are
deleted along with the retired correlation predicates.

* test(codex): reset the shared binding store between thread-lifecycle tests

SQLite bindings are keyed by session identity rather than the per-test
temp dir, so earlier tests leaked resumable threads into fresh-start
expectations. The old silent resume-failure fallback masked the leak;
subscription safety surfaces it.

* test(codex): reset the binding store between delivery-hint iterations

The loop reuses one session identity across iterations, so the previous
iteration's thread would resume against a harness that cannot serve it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: codex maintainer Maintainer-authored PR merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XL status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex app-server: keyed turn routing, client-scoped rate limits, and resume subscription safety

1 participant