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:
- first sampling invokes each contributor exactly once;
- the mock Responses server receives one request;
- 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.
Environment
26.818.41509(build6962)codex-cli 0.149.0-alpha.4.197e7c55e2b64738ec6fe2311ad77a60b106fefaeDarwin 27.0.0 arm64 armI also verified that the same control flow is still present on current
mainat184ff338ad7d737b0bd6d5e913a070cfd5c666bd.What issue are you seeing?
run_turnbuilds world state twice for the exact same initialStepContextbefore the first sampling request.In three independently affected Desktop tasks, tracing had the same shape:
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:Register it with
ExtensionRegistryBuilder::prompt_contributor, submit one turn to the mock Responses server, and assert: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_turnthen never reaches the mock server.Expected behavior
The world state produced from the initial
StepContextshould 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_turncapturesfirst_step_contextand immediately builds/records its world state throughrecord_context_updates_and_set_reference_context_item:turn.rslines 206-248It then stores that same context in
next_step_context, takes it on the first loop iteration, and unconditionally callsrecord_step_world_state_if_changedbefore sampling:turn.rslines 300-367record_step_world_state_if_changedThat refresh calls
build_world_state_for_stepagain. 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)fromnext_step_context.take()-> reuse the initial snapshot;StepContextcaptured inside the loop -> refresh world state as before.Observed result across three affected histories:
The added regression test
first_sampling_reuses_initial_world_state_snapshotalso passes with the minimal fix:Suggested regression coverage:
StepContextinvokes 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.