Skip to content

Codex Desktop reasoning level resets from xhigh/high to low in the same active thread after delegations/continuations #26930

Open

Description

@jimmycarp966

What version of the Codex App are you using (From “About Codex” dialog)?

Codex App 26.602.40724 (CFBundleVersion 3593) from /Applications/Codex.app.

What subscription do you have?

ChatGPT Pro subscription, USD 200/month.

What platform is your computer?

Darwin 25.5.0 arm64 arm

What issue are you seeing?

In Codex Desktop, the reasoning/thinking level keeps resetting to low inside the same active thread, even after I manually set it to high / xhigh.

This is not only a new-chat/default-setting issue. It happens during an ongoing operational thread with active delegated Codex threads/workers. I set the level to extremely high, continue working, and shortly after a delegation/update/continuation it shows as low again without me changing it.

Why this matters: I use Codex as an operational coordinator across multiple live project threads. Some tasks involve production systems, SQL/Supabase, Vercel/env, auth/secrets, RRHH/payroll, cash/stock/sales/accounting, and push/main decisions. For these workflows, reasoning effort must not silently drop to low, because the user may believe the thread is still running with high/xhigh reasoning.

What steps can reproduce the bug?

  1. Open a Codex Desktop thread.
  2. Set reasoning/thinking level to xhigh or high.
  3. Use the thread as a coordinator and send/receive codex_delegation messages to/from other active Codex threads.
  4. Continue working while background captain/worker threads are active.
  5. Observe that the same thread’s reasoning level changes back to low without the user selecting low.

Active thread IDs at time of issue:

Current/active coordination thread:

  • 019ea4f4-7990-7e32-a582-8ae8c3640303 — Coordinar capitanía general

Active captain/worker threads involved:

  • 019e8074-ed13-7193-be78-eae91235c9f9 — Capitán - Avícola del Sur
  • 019ea4ea-e759-7290-9579-11f5b206913b — Worker - Avícola producción viva QA integral no-RRHH
  • 019e8081-6810-70d2-a27e-40d8c5697024 — Capitán - Turilo ERP
  • 019ea4ea-fa86-7af0-b0ee-bca2cf822480 — Worker - Turilo producción viva entrega mañana

Related/historical CAPITAN GENERAL thread used by delegations:

  • 019e7c7d-b636-7fe0-98a8-e20dab577fb1 — CAPITAN GENERAL

What is the expected behavior?

If I manually set reasoning to xhigh or high in an active Codex Desktop thread, it should remain at that level unless I explicitly change it.

At minimum, Codex should not silently reset the visible/current reasoning effort to low during the same thread after:

  • thread continuation,
  • context compaction,
  • receiving codex_delegation messages,
  • reading/sending messages to background threads,
  • active worker/captain updates,
  • or returning from tool activity.

Additional information

This may be related to thread rehydration, continuation, context compaction, or delegation handling resetting the active mode settings.

This is similar in spirit to existing reasoning-effort persistence issues, but the observed bug is specifically:

  • Codex Desktop
  • same active thread
  • user manually set high/xhigh
  • resets to low later without user action
  • during delegation/background-thread workflows

Related issues:

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    subagentIssues involving subagents or multi-agent features
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on Jun 8, 2026
  2. github-actions commented on Jun 8, 2026

    @github-actions
    Contributor
  3. Jackten commented on Jun 11, 2026

    @Jackten

    I found a narrower app-server-level reproduction that may explain at least one class of the reasoning-effort resets described here.

    Environment:

    • Codex App 26.608.12217 (CFBundleVersion 3722)
    • Bundled CLI codex-cli 0.138.0-alpha.7
    • macOS Darwin 25.5.0 arm64 arm

    Observed path:

    1. Initialize app-server as clientInfo.name = "Codex Desktop".
    2. Start or resume a thread.
    3. Set the thread effort intentionally with thread/settings/update, for example high.
    4. Send a later turn/start for the same thread where the request still carries a stale effort, for example low.
    5. The stale turn/start.effort is treated as a thread-settings override, so the next Responses request uses low even though the user did not explicitly change the setting back.

    Expected behavior:

    • Explicit user picker changes should persist through thread/settings/update.
    • A normal Codex Desktop turn/start should not silently overwrite the saved thread effort with stale composer state.

    I pushed a small fork branch with a regression test and candidate app-server fix here:

    https://github.com/Jackten/codex/tree/per-thread-reasoning-effort

    The test added is codex_desktop_turn_start_effort_does_not_override_thread_settings, and it verifies that after thread/settings/update sets effort to high, a later Codex Desktop turn/start carrying stale low still sends reasoning.effort = high to Responses.

    Local validation I ran:

    • just fmt
    • just test -p codex-app-server (842 passed, 1 skipped)
    • just fix -p codex-app-server
    • release build of codex-cli

    I did not open a PR because docs/contributing.md says unsolicited external PRs will be closed without review. If this root cause matches the intended behavior, I can open a PR or adjust the branch if invited.

  4. ghitafilali commented on Jun 20, 2026

    @ghitafilali

    I can reproduce a closely related reset on current Codex Desktop, and the local state suggests there may be two separate persistence paths involved.

    Environment:

    • Codex Desktop 26.616.32156 (CFBundleVersion 4157)
    • Bundled CLI codex-cli 0.142.0-alpha.1
    • macOS app, ChatGPT auth

    User config already forces xhigh:

    model = "gpt-5.5"
    model_reasoning_effort = "xhigh"
    plan_mode_reasoning_effort = "xhigh"
    service_tier = "priority"

    Observed local state:

    • Active visible thread metadata is mostly persisted as high: high=780, xhigh=52, low=4, medium=1.
    • Two affected user threads that were explicitly changed in the UI to Extra High still have persisted metadata reasoning_effort='high':
      • 019e7d97-2e06-7e11-8d9a-447a11d5c390 ($do .../issues/364) -> high
      • 019e7fde-ecdb-7511-a367-0c66f1d95383 ($do .../issues/362) -> high
    • The app logs show the UI did send XHigh settings updates for those threads:
      • 2026-06-19 21:23:01 thread 019e7d97-...: op: ThreadSettings { ... model: Some("gpt-5.5"), effort: Some(Some(XHigh)) ... }
      • 2026-06-19 21:22:56 thread 019e7fde-...: op: ThreadSettings { ... model: Some("gpt-5.5"), effort: Some(Some(XHigh)) ... }
    • A related active thread had an actual later turn run as high:
      • codex.turn.reasoning_effort=high
      • codex.request.reasoning_effort=high
      • then a later ThreadSettings update for the same thread set effort: Some(Some(XHigh)), but the persisted thread row remained high.

    Source-level observations from current main (64bdeed9f7adbe60c725153b3fb74ed044a36221):

    • app-server/src/request_processors/turn_processor.rs passes params.effort from turn/start directly into build_thread_settings_overrides, so a stale Desktop turn/start.effort can still override an intentional setting. This matches the narrower reproduction in the previous comment.
    • app-server/src/bespoke_event_handling.rs handles EventMsg::ThreadSettingsApplied by emitting thread/settings/updated, but I do not see it patching thread metadata.
    • Persisted metadata appears to get reasoning_effort from turn context/history:
      • thread-store/src/thread_metadata_sync.rs: update.reasoning_effort = turn_ctx.effort.clone()
      • state/src/extract.rs: metadata.reasoning_effort = turn_ctx.effort.clone()
    • On resume, request_processors/thread_processor.rs::merge_persisted_resume_metadata merges persisted metadata.reasoning_effort back into model_reasoning_effort.

    That would explain the user-visible behavior:

    1. User changes an existing Desktop thread from High to Extra High.
    2. Core session accepts ThreadSettings ... XHigh.
    3. Thread metadata/list row still says high because it reflects last turn context, not the settings update.
    4. On resume/reload, persisted high is merged back into config and the session appears to have reverted.
    5. If a later turn/start also carries stale effort: high, it can reinforce the downgrade.

    Expected behavior: an explicit per-thread UI setting to Extra High should persist across sidebar reloads, thread resume, wakeups/continuations, and future turns unless the user changes it again. A stale turn/start.effort from the Desktop client should not lower an already-explicit thread setting, and thread/settings/update/ThreadSettingsApplied should update the durable thread metadata or resume should avoid treating last-observed turn effort as the authoritative current thread setting.

    Potential fix shape:

    • Keep/land the app-server guard from the previous repro: Desktop turn/start should not let stale params.effort override saved thread settings.
    • Also persist the effective ThreadSettingsApplied.thread_settings.reasoning_effort into thread metadata, or distinguish "last observed turn effort" from "current explicit thread setting" so resume/list cannot resurrect the old value.
  5. ghitafilali commented on Jun 20, 2026

    @ghitafilali

    Still reproducible on a newer Desktop build, with evidence that this is not just UI display state.

    Environment:

    • Codex Desktop 26.616.41845 (CFBundleVersion 4198) from /Applications/Codex.app
    • Global config still forces xhigh:
      • model_reasoning_effort = "xhigh"
      • plan_mode_reasoning_effort = "xhigh"

    Additional observations from local state/logs:

    • Before local mitigation, visible unarchived thread rows in ~/.codex/state_5.sqlite were still mostly persisted as high: high=781, xhigh=61, low=4, medium=1, blank=1.
    • The previously reported threads still had persisted reasoning_effort='high' even after later explicit XHigh UI updates:
      • 019e7d97-2e06-7e11-8d9a-447a11d5c390 (issue 364) was high; logs show explicit ThreadSettings ... effort: Some(Some(XHigh)) at 2026-06-19 21:22:23, 2026-06-19 21:23:01, and 2026-06-20 11:28:14.
      • 019e7fde-ecdb-7511-a367-0c66f1d95383 (issue 362) was high; logs show explicit ThreadSettings ... effort: Some(Some(XHigh)) at 2026-06-19 21:22:28, 2026-06-19 21:22:56, and 2026-06-20 11:28:09.
    • A newer affected thread also actually ran with high effort:
      • 019ee549-215d-7750-85a3-a31e5d6b6208 (issue 545) had codex.turn.reasoning_effort=high and codex.request.reasoning_effort=high at 2026-06-20 13:56:59, despite the user expectation that all active sessions are xhigh.

    I think there are two related persistence paths involved:

    1. The stale Desktop turn/start.effort override path described by @Jackten is real. In current origin/main, codex-rs/app-server/src/request_processors/turn_processor.rs still passes params.effort into build_thread_settings_overrides for method: "turn/start"; build_thread_settings_overrides then treats any non-None effort as a settings override. Jackten's candidate commit 0f573314e6678e5b76b7af71b431831c992e16d3 addresses this class by ignoring turn/start.effort for clientInfo.name == "Codex Desktop" and adds codex_desktop_turn_start_effort_does_not_override_thread_settings.

    2. There also appears to be a durable metadata gap after an explicit settings update. EventMsg::ThreadSettingsApplied in codex-rs/app-server/src/bespoke_event_handling.rs updates in-memory thread state and emits thread/settings/updated, but does not update the persistent thread metadata row. Separately, codex-rs/thread-store/src/thread_metadata_sync.rs records TurnContext.effort into ThreadMetadataPatch.reasoning_effort, and codex-rs/app-server/src/request_processors/thread_processor.rs later uses persisted metadata.reasoning_effort to set model_reasoning_effort during resume. That means a user can set XHigh successfully in the UI, but if the latest persisted turn context was High, resume/list rehydration can continue to present or restore High.

    Suggested fix shape:

    • Land the stale-Desktop-turn/start.effort guard from Jackten's branch, adjusted for current origin/main.
    • Also persist the effective reasoning effort from ThreadSettingsApplied, or better split "current explicit per-thread reasoning setting" from "latest observed turn reasoning effort" so rollout-derived metadata cannot overwrite the user's selected setting.
    • Add a regression that starts/runs High, applies thread/settings/update to XHigh without a subsequent turn, then resumes/lists the thread and expects XHigh.

    Impact: this is user-visible and can silently lower actual request effort. The user believes a session is Extra High, but logs show some requests are sent as Medium, which is extremely frustrating.

  6. ferueda commented on Jul 14, 2026

    @ferueda

    Controlled reproduction: send_message_to_thread copies sender effort into recipient callback turn

    I can reproduce the delegation-related reasoning reset described here with a fresh, read-only A/B test. The result is not only UI state: the recipient's rollout records the changed thread_settings_applied event and the callback turn actually runs at the sender's effort.

    Environment

    • Codex in ChatGPT desktop: 26.707.71524 (bundle 5263)
    • Bundled CLI: codex-cli 0.144.2
    • Subscription: ChatGPT Pro
    • Platform: Darwin 25.5.0 arm64 arm
    • Model in every test thread: gpt-5.6-sol

    Test protocol

    1. Create a fresh parent thread at max, let its initial no-op turn complete, and leave it idle.
    2. Create a fresh child thread at the test effort.
    3. Have the child call:
    send_message_to_thread({
      threadId: "<parent-thread-id>",
      prompt: "<fixed marker>"
      // model and thinking intentionally omitted
    })
    1. Inspect the recipient parent's rollout around the resulting codex_delegation.
    2. Repeat with a max-effort child as the control.

    No thread inspected or modified repository files. No user changed either parent's settings between turns.

    Parent before callback Child sender Parent callback turn
    Sol / max Sol / medium Sol / medium
    Sol / max Sol / max Sol / max

    Reproduction evidence

    Disposable sessions:

    • Parent: 019f5f05-1ea5-7ee3-acac-4dffdcd36ec7
    • Medium child: 019f5f05-508a-7b03-8aa3-884e9f9deecd

    Sanitized parent event sequence:

    2026-07-14T05:06:40.085Z  turn_context              model=gpt-5.6-sol effort=max
    2026-07-14T05:07:02.044Z  thread_settings_applied   model=gpt-5.6-sol reasoning_effort=medium
    2026-07-14T05:07:02.058Z  turn_context              model=gpt-5.6-sol effort=medium
    2026-07-14T05:07:02.064Z  codex_delegation received from medium child
    

    Control evidence

    Disposable sessions:

    • Parent: 019f5f06-1e51-7601-9e43-68f8cc9a136c
    • Max child: 019f5f07-04c9-7923-984b-a53f5d6cca59
    2026-07-14T05:07:45.548Z  turn_context              model=gpt-5.6-sol effort=max
    2026-07-14T05:08:55.124Z  thread_settings_applied   model=gpt-5.6-sol reasoning_effort=max
    2026-07-14T05:08:55.138Z  turn_context              model=gpt-5.6-sol effort=max
    2026-07-14T05:08:55.144Z  codex_delegation received from max child
    

    This isolates sender effort as the changing variable. It also rules out a permission-profile transition in this reproduction: both callback settings events had no active permission profile.

    Expected

    Cross-thread message delivery should not mutate the recipient thread's model or reasoning effort when send_message_to_thread omits model/thinking overrides. The max parent should process the callback at max unless the user explicitly changes that parent.

    Actual

    The callback turn's effort matches the sending child. A medium executor can therefore silently downgrade a max orchestrator, and a different sender effort can silently alter an otherwise pinned recipient.

    One additional contract clue: tool guidance exposed to different threads was inconsistent—one description said omitted settings preserve the target thread, while another said they inherit the calling thread's compatible settings. Runtime behavior in this test matches sender inheritance. This looks like sender settings being carried into the destination turn rather than content-only delivery.

    I originally noticed the issue when a long-running max orchestrator changed to medium immediately after a medium executor checkpoint. The disposable reproduction above confirms the trigger without relying on that private operational thread. Full rollouts are intentionally not attached because they contain unrelated local context; the event sequence and disposable session IDs above should allow internal inspection.

  7. psychananaz commented on Jul 28, 2026

    @psychananaz

    is there a single human person in this thread at all, lmao.

  8. Jackten commented on Jul 28, 2026

    @Jackten
  9. kyleAmadeous commented on Jul 31, 2026

    @kyleAmadeous

    I traced a separate, deterministic thread/settings/update writer that matches the reported parent-thread downgrade and is still present on current main (4642370542739d5dd080b0c87a9de06a6435d3db).

    Evidence

    The affected rollout was produced by originator=codex-tui, cli_version=0.144.1. A parent running gpt-5.6-sol/xhigh recorded this sequence during an already-active turn:

    2026-07-14T10:15:16.274Z thread_settings_applied model=gpt-5.6-luna reasoning_effort=xhigh
    2026-07-14T10:15:16.276Z thread_settings_applied model=gpt-5.6-luna reasoning_effort=low
    

    The next explicit turn then started as Luna/low. The two settings events being 2 ms apart is significant: the TUI emits a model-only update first (preserving xhigh), followed by an effort-only low update.

    Root writer

    On rust-v0.144.1, the first writer is the stale safety-buffer retry branch in codex-rs/tui/src/app/event_dispatch.rs (lines 340-356). On current main, the same logic lives in codex-rs/tui/src/app/safety_buffering.rs:

    if !self.chat_widget.can_retry_safety_buffered_turn(&turn_id) {
        self.app_event_tx.send(AppEvent::UpdateModel(model));
        self.app_event_tx.send(AppEvent::UpdateReasoningEffort(Some(
            ReasoningEffortConfig::Low,
        )));
        return;
    }

    model is the one-off safety-buffering fasterModel (Luna in the reproduction), while low is hard-coded by the TUI. If agent output starts before the user accepts the retry, the retry becomes stale, but this branch turns those one-off retry settings into global UI setting events.

    The exact path is:

    SafetyBufferingEvent.faster_model
      -> RetrySafetyBufferedTurn
      -> stale retry branch emits UpdateModel + UpdateReasoningEffort(Low)
      -> sync_active_thread_*_setting
      -> ThreadSettingsUpdateParams(active parent thread id)
      -> thread/settings/update (twice)
      -> Op::ThreadSettings
      -> thread_settings_applied (twice)
    

    A subsequent turn/start then reuses the contaminated local TUI settings. This is distinct from the sender-effort delegation path and from the stale Desktop turn/start.effort path discussed above.

    Candidate fix and regression

    I prepared an atomic fix that makes a stale retry a no-op instead of applying its one-off model/effort to the parent. Normal, still-valid retries continue to fork using the faster model and low effort.

    Branch: https://github.com/kyleAmadeous/codex/tree/fix/kyle/tui-parent-settings-safety-retry
    Commit: kyleAmadeous@666b6cc

    The regression configures the parent as Sol/xhigh and the stale retry as Luna/low, then verifies that no persistent UpdateModel or UpdateReasoningEffort event is emitted and the parent remains Sol/xhigh.

    Pre-fix, the new test failed with:

    stale worker retry must not enqueue persistent parent settings: UpdateModel("gpt-5.6-luna")
    

    Post-fix validation:

    • focused regression: 1 passed
    • safety retry tests: 7 passed
    • just test -p codex-tui: 3304 passed, 4 skipped
    • just fix -p codex-tui
    • just fmt
    • git diff --check

    Per docs/contributing.md, I have not opened an unsolicited PR. If a maintainer confirms this approach and invites the contribution, I can open the PR immediately from the prepared branch.

  10. naipi11 commented on Aug 19, 2026

    @naipi11

    Until the thread setting is preserved correctly, the safest manual guard is to treat the composer value as a per-turn setting:

    1. Let the current turn finish.
    2. After any delegation, continuation, or background callback, reopen the model picker and reselect the intended reasoning effort.
    3. Confirm the collapsed composer control still shows that effort immediately before sending the next message.
    4. Repeat the check after every callback that wakes the thread.

    Verification: the next turn should start with the intended effort in the UI/turn metadata, if metadata is available. Limitation: this does not repair persistence, and a turn that already started at the lower effort cannot be upgraded retroactively.

  11. przadka commented on Sep 1, 2026

    @przadka

    Linux CLI 0.149.0 has a deterministic automatic-goal-continuation variant of this reset.

    The same process and rollout started with -c model_reasoning_effort=ultra. Its first four turn_context records were ultra. After the first goal turn completed, the automatic continuation emitted:

    2026-08-31T09:10:29.113Z  thread_settings_applied  reasoning_effort=xhigh
    2026-08-31T09:10:31.624Z  turn_context             effort=xhigh
    

    All 19 later turn contexts recorded xhigh. There was no process restart or user model-selection event at that boundary.

    Expected: an automatic goal continuation preserves the thread's effective CLI override unless the user explicitly changes it. For a thread launched with model_reasoning_effort=ultra, the continuation should not apply the global xhigh setting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingsessionIssues involving session (thread) management, resuming, forking, naming, archivingsubagentIssues involving subagents or multi-agent features

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions