Skip to content

[macOS Desktop] Initial StepContext rebuilds world state before first sampling and can wedge turns #40269

Description

@GravityPoet

Environment

  • Codex App: 26.818.41509 (build 6962)
  • Codex runtime: codex-cli 0.149.0-alpha.4.1
  • Observed source base: 97e7c55e2b64738ec6fe2311ad77a60b106fefae
  • Platform: Darwin 27.0.0 arm64 arm
  • Subscription: omitted because the failure is entirely local and occurs before any model request is sent

I also verified that the same control flow is still present on current main at 184ff338ad7d737b0bd6d5e913a070cfd5c666bd.

What issue are you seeing?

run_turn builds world state twice for the exact same initial StepContext before the first sampling request.

In three independently affected Desktop tasks, tracing had the same shape:

initial world_state.build completes
second world_state.build starts for the same initial step
no run_sampling_request trace is entered
no assistant response is produced

The second build remained pending inside a context contributor, so the UI stayed in progress indefinitely without an error. Minimal prompts with reasoning disabled stopped at the same phase, so prompt wording and model reasoning were not involved.

After changing the first loop iteration to reuse the world-state snapshot already built for first_step_context, all three affected histories entered sampling and produced real assistant replies.

Steps to reproduce the duplicate build

A deterministic core regression test can register a counting ContextContributor:

struct WorldStateBuildCountExtension {
    calls: Arc<AtomicUsize>,
}

impl ContextContributor for WorldStateBuildCountExtension {
    fn contribute_world_state<'a>(
        &'a self,
        _input: WorldStateContributionInput<'a>,
    ) -> ExtensionFuture<'a, Vec<WorldStateSectionContribution>> {
        self.calls.fetch_add(1, Ordering::Relaxed);
        Box::pin(async { Vec::new() })
    }
}

Register it with ExtensionRegistryBuilder::prompt_contributor, submit one turn to the mock Responses server, and assert:

assert_eq!(response_mock.requests().len(), 1);
assert_eq!(calls.load(Ordering::Relaxed), 1);

On current upstream control flow, the second assertion observes 2: the contributor is called twice before the first model request. To reproduce the user-visible wedge, have the contributor remain pending on its second invocation; submit_turn then never reaches the mock server.

Expected behavior

The world state produced from the initial StepContext should be reused for the first sampling request.

When a later loop iteration captures a new StepContext (for example after a tool step, steer, or continuation), world state should still be refreshed before that later sampling request.

Technical diagnosis

run_turn captures first_step_context and immediately builds/records its world state through record_context_updates_and_set_reference_context_item:

It then stores that same context in next_step_context, takes it on the first loop iteration, and unconditionally calls record_step_world_state_if_changed before sampling:

That refresh calls build_world_state_for_step again. Since the build awaits extension contributors, a contributor that does not return on the redundant invocation prevents the first sampling request.

The exact production contributor that remained pending was not isolated. However, the duplicate invocation is unconditional and independently reproducible. Disabling skill search, skill instructions, and environment-context inclusion during bisection did not remove the failure.

Validation of a minimal local fix

The local fix tracks whether the selected step context already produced the current snapshot:

  • Some(first_step_context) from next_step_context.take() -> reuse the initial snapshot;
  • a StepContext captured inside the loop -> refresh world state as before.

Observed result across three affected histories:

Runtime World-state builds before first sampling Entered sampling Real assistant reply
Upstream control flow 2 no no
Local minimal fix 1 yes yes

The added regression test first_sampling_reuses_initial_world_state_snapshot also passes with the minimal fix:

test suite::remote_env::first_sampling_reuses_initial_world_state_snapshot ... ok
test result: ok. 1 passed; 0 failed

Suggested regression coverage:

  1. first sampling invokes each contributor exactly once;
  2. the mock Responses server receives one request;
  3. a later sampling step with a newly captured StepContext invokes contributors again.

Additional information

This is distinct from #40178, which concerns duplicate paginated-history ordinals and a stale SQLite projection after restart. This report is a pre-sampling core-loop stall and does not require history corruption.

Thread IDs, turn IDs, call IDs, absolute paths, prompts, tool contents, MCP inventory, and raw JSONL/SQLite logs are intentionally omitted.

Activity

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

    agentIssues related to the core agent loopappIssues related to the Codex desktop appbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions