Skip to content

fix(agents): safe codex tool-terminal continuation, auth-true simple-completion routes, doctor opt-out#108966

Merged
steipete merged 3 commits into
mainfrom
claude/codex-round-d1
Jul 16, 2026
Merged

fix(agents): safe codex tool-terminal continuation, auth-true simple-completion routes, doctor opt-out#108966
steipete merged 3 commits into
mainfrom
claude/codex-round-d1

Conversation

@steipete

Copy link
Copy Markdown
Contributor

What Problem This Solves

Three verified codex-cluster defects on current main:

  1. [Bug]: codex app-server: tool-call-terminal turn (stopReason=toolUse, no final assistant) dies with no retry — needs safe continuation, not replay #108517 — a codex app-server turn ending stopReason=toolUse with no final assistant message dies with "Agent stopped before producing a final response… some tool actions may have already been executed" and no recovery, even though codex-rs treats that turn as a clean completion (handle_turn_complete emits turn/completed status=completed without requiring a final agent message).
  2. OpenAI simple-completion (utility model) pairs api_key auth with the ChatGPT-backend Codex transport — narration/titles 401 #104779 — simple-completion (narration, chat titles, labels) resolves the model route before auth, so a dual-route OpenAI model materialized on the ChatGPT-backend codex transport can be paired with an openai:api-key profile → silent 401s. Upstream contract: codex-rs to_api_provider(auth_mode) derives the endpoint from the auth mode.
  3. doctor --fix silently adds and enables the uninstalled codex plugin (routing-altering) when openai is enabled #97180 (residual)openclaw doctor --fix flipped an explicit plugins.entries.codex.enabled: false to true. Deny-list and global disable were respected; an explicit per-entry opt-out was not.

Why This Change Was Made

  • Safe continuation, not replay: one bounded continuation attempt fires only when the terminal turn is provably settled — zero visible payloads, no error/abort/timeout, and every toolCall id in the terminal assistant has a matching non-error toolResult in the message snapshot. Lifecycle counters are attempt-cumulative and alias across batches, so completion is proven per tool-call id; undispatched or partially dispatched batches keep the existing fail-closed error. The continuation appends a fresh native-thread turn (codex turn/start appends a new user turn; completed tools are never re-sent), so replaySafe deliberately does not gate it — invariant documented inline.
  • Auth before route: for official dual-route OpenAI models, simple-completion now resolves auth first, selects the auth-compatible route via the existing selectOpenAIModelRouteAuth + buildAgentRuntimeAuthPlan + materializePreparedRuntimeModel seam (no parallel resolver, no URL swaps), then re-validates auth against the materialized route. isAuthModeAllowedForModel is now symmetric: the codex transport requires subscription OAuth, so api-key profiles fail closed with a clear error instead of a 401. Custom base URLs and explicit refs are untouched.
  • Explicit opt-out wins: doctor treats plugins.entries.codex.enabled: false like the deny case — warn with a fix hint, never mutate. Missing-entry and restrictive-allowlist repairs stay automatic (the managed-harness contract from Fix doctor repair for disabled Codex runtime plugin #82502 is preserved).

User Impact

  • Codex turns that finish on tool calls now produce the final answer instead of an error, without any risk of double-executing side effects.
  • Narration/titles/labels work on Codex-OAuth-primary setups with an api-key profile present; misconfigured pairings fail loudly at auth resolution instead of silently at request time.
  • Doctor respects an operator's explicit codex plugin disable and says what manual action would be needed.

Evidence

  • Focused suites green after rebase onto current main: run.incomplete-turn.test.ts (136, incl. new continuation success / exhaustion / never-started / partial-dispatch cases), simple-completion-runtime.test.ts (26: api-key→platform route, oauth→codex route kept, custom baseUrl untouched, explicit ref honored), model-auth.profiles.test.ts (22), doctor route-warnings + core-checks (234+126).
  • codex-rs contract personally verified at 800715d201: bespoke_event_handling.rs (clean completion without final message), turn_processor.rs + core/src/session/mod.rs (turn/start appends, never replays), model-provider-info/src/lib.rs (endpoint = f(auth mode)).
  • Autoreview (gpt-5.6-sol, xhigh): iterated three times — it caught idle≠completed, cumulative-counter aliasing, and a wire-shape discriminator bug in the continuation guard; final pass clean. Remote Testbox pnpm tsgo:core green on the pre-hardening state; hosted CI validates the final head.

Fixes #108517
Fixes #104779
Fixes #97180

…tes, doctor opt-out

Three verified codex-cluster fixes:

- Tool-call-terminal turns (stopReason=toolUse, no final assistant message)
  now get one bounded continuation instead of dying with an incomplete-turn
  error. Completion is proven per tool-call id (every terminal toolCall needs
  a non-error toolResult in the snapshot) so undispatched or partially
  dispatched batches keep failing closed; the continuation appends a fresh
  native-thread turn and never replays completed tools. (#108517)
- Simple-completion (narration/titles/labels) resolves auth before finalizing
  official dual-route OpenAI models and re-materializes the auth-compatible
  physical route through the runtime-plan seam; api-key credentials are no
  longer legal for the ChatGPT-backend codex transport (fail closed), ending
  silent 401s. Custom base URLs and explicit refs stay honored. (#104779)
- openclaw doctor --fix no longer flips an explicit plugins.entries.codex
  enabled:false; explicit user opt-out now warns with a fix hint like the
  deny-list case, while missing-entry and allowlist repairs stay automatic. (#97180)

Fixes #108517
Fixes #104779
Fixes #97180
@steipete
steipete requested a review from a team as a code owner July 16, 2026 11:46
@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations agents Agent runtime and tooling size: L maintainer Maintainer-authored PR labels Jul 16, 2026

@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: d5680f408c

ℹ️ 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 thread src/agents/model-auth.ts
Comment on lines +151 to +153
if (openAICodexTransportRequiresOAuth(params)) {
return params.mode === "oauth";
}

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 Accept token profiles for Codex subscription routes

When a user has an OpenAI token profile, this check now rejects it for openai-chatgpt-responses even though OpenClaw treats oauth and token as subscription auth (src/agents/provider-model-route-auth.ts:107-109) and the Codex app-server contract explicitly accepts an OpenAI OAuth or token profile for prepared subscription routes (extensions/codex/src/app-server/run-attempt-connection.ts:185-187, covered by extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts:428-456). This breaks users who authenticated with openclaw models auth paste-token --provider openai by failing getApiKeyForModel/simple-completion route materialization before the valid token profile can be handed to Codex; allow mode === "token" here while still rejecting API-key auth.

Useful? React with 👍 / 👎.

… messagesSnapshot, update sibling hints

The symmetric codex-transport auth gate now accepts both subscription
credential classes (oauth and ChatGPT token) instead of oauth only, so
prepared-candidate fallback keeps skipping to a valid subscription profile.
attempt-result threads messagesSnapshot into the incomplete-turn attempt
shape, and the doctor-lint sibling test expects the manual hint now that an
explicit plugins.entries.codex.enabled=false blocks auto-repair.

@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: 8269b96d7b

ℹ️ 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 +328 to +332
const routeAuthDecision = selectOpenAIModelRouteAuth({
resolution: routeResolution,
sourcePlan: buildProviderModelAuthSourcePlan({
ownership: { reason: "provider-binding", source },
profiles: [],

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 Respect configured OpenAI auth before locking the route

With models.providers.openai.auth: "api-key" and only an ambient OPENAI_API_KEY plus an automatic ChatGPT/OAuth profile, the earlier resolveApiKeyForProvider call has no modelApi or configured-auth filter, so it can return the OAuth profile; this then wraps that first credential as a required provider-binding, and selectOpenAIModelRouteAuth can no longer apply the configured API-key route the main runtime applies in src/agents/runtime-plan/prepare-auth.ts. Simple completions can therefore keep using the subscription/Codex route despite explicit API-key config, so keep automatic route selection aligned with the runtime plan instead of binding the first unfiltered credential.

Useful? React with 👍 / 👎.

@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: 7d1d83480d

ℹ️ 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 +754 to +759
(params.attempt.messagesSnapshot ?? []).flatMap((message) => {
const result = message as { role?: unknown; toolCallId?: unknown; isError?: unknown };
return result.role === "toolResult" &&
result.isError !== true &&
typeof result.toolCallId === "string"
? [result.toolCallId]

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 Scope tool-result completion to the terminal assistant

Here the completion proof accepts any non-error toolResult in messagesSnapshot, but that snapshot is the full session transcript (attempt-stream-settle.ts snapshots activeSession.messages) rather than just results following the terminal assistant. If a provider/runtime repeats a toolCall id used in an earlier turn, a stale successful result can satisfy completedToolCallIds even when the current terminal toolUse request never ran, so the new continuation prompts the model not to repeat the tool and can skip a side-effecting action. Scope the result lookup to the current assistant's following span, or otherwise verify the result belongs to this terminal turn.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 2848acb into main Jul 16, 2026
118 checks passed
@steipete
steipete deleted the claude/codex-round-d1 branch July 16, 2026 12:36
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 17, 2026
…completion routes, doctor opt-out (openclaw#108966)

* fix(agents): safe codex continuation, auth-true simple-completion routes, doctor opt-out

Three verified codex-cluster fixes:

- Tool-call-terminal turns (stopReason=toolUse, no final assistant message)
  now get one bounded continuation instead of dying with an incomplete-turn
  error. Completion is proven per tool-call id (every terminal toolCall needs
  a non-error toolResult in the snapshot) so undispatched or partially
  dispatched batches keep failing closed; the continuation appends a fresh
  native-thread turn and never replays completed tools. (openclaw#108517)
- Simple-completion (narration/titles/labels) resolves auth before finalizing
  official dual-route OpenAI models and re-materializes the auth-compatible
  physical route through the runtime-plan seam; api-key credentials are no
  longer legal for the ChatGPT-backend codex transport (fail closed), ending
  silent 401s. Custom base URLs and explicit refs stay honored. (openclaw#104779)
- openclaw doctor --fix no longer flips an explicit plugins.entries.codex
  enabled:false; explicit user opt-out now warns with a fix hint like the
  deny-list case, while missing-entry and allowlist repairs stay automatic. (openclaw#97180)

Fixes openclaw#108517
Fixes openclaw#104779
Fixes openclaw#97180

* fix(agents): accept ChatGPT token profiles on codex transport, thread messagesSnapshot, update sibling hints

The symmetric codex-transport auth gate now accepts both subscription
credential classes (oauth and ChatGPT token) instead of oauth only, so
prepared-candidate fallback keeps skipping to a valid subscription profile.
attempt-result threads messagesSnapshot into the incomplete-turn attempt
shape, and the doctor-lint sibling test expects the manual hint now that an
explicit plugins.entries.codex.enabled=false blocks auto-repair.

* chore(agents): route test mock casts through unknown, un-export unused doctor helper
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations maintainer Maintainer-authored PR size: L

Projects

None yet

1 participant