Repository navigation
Responses Lite: changing reasoning effort can strand a large session on “Request blocked.” until compaction #32533
Description
What version of Codex CLI is running?
codex-cli 0.144.0, from a custom fork based on current openai/codex source.
The relevant incremental WebSocket and error-handling paths described below are present on upstream main at 9e552e9d15ba52bed7077d5357f3e18e330f8f38.
What subscription do you have?
Pro 20x.
Which model were you using?
The failure started on GPT-5.6 Sol after changing reasoning effort from low to high. The affected GPT-5.6 model configuration uses Responses Lite over the Responses WebSocket.
Retries also failed with:
- GPT-5.6 Sol, high and low effort
- GPT-5.6 Terra, high effort
After compaction, GPT-5.5 worked, and switching back to GPT-5.6 Sol high also worked. This does not appear to be a general outage or a prompt-content rejection.
What platform is your computer?
Linux 6.17.0-35-generic x86_64.
What terminal emulator and version are you using (if applicable)?
Ghostty 1.3.1 (TERM=xterm-ghostty), no tmux/zellij.
Codex doctor report
Not applicable. The rollout and diagnostic DB contain a deterministic transition from an incremental request to a full-history request, followed by repeated top-level Responses errors.
Affected thread ID:
019f54e4-c359-7f33-9d38-be0d864eb664
What issue are you seeing?
A long Responses Lite session worked while requests could extend the cached response incrementally. Changing reasoning effort invalidated WebSocket request reuse, so Codex resent the complete history. That request returned:
■ Request blocked.
Every subsequent turn—including ping and continue, different GPT-5.6 variants, new WebSocket connections, and an Esc rollback—returned the same response. The session recovered only after compaction reduced the context substantially.
The important sequence was:
| State | Estimated context | Request behavior | Result |
|---|---|---|---|
| Last stable turn before investigation | ~163,160 tokens | Incremental | Success |
| After fetching several prior GitHub issue bodies | ~172,528 tokens | Incremental | Success |
| Reasoning effort changed low → high | ~172,528 tokens | Full resend | Request blocked. |
| Two-turn rollback | ~163,146 tokens | Full resend | Still blocked |
| Compacted context | ~36,451 tokens | New baseline | Success |
The diagnostic log records the exact reuse transition immediately before the first failure:
incremental request failed, websocket reuse properties didn't match
unhandled responses event: error
Turn error: Request blocked.
There were 12 blocked attempts over approximately four and a half minutes. Later attempts used fresh WebSocket connections, so the failure was not confined to one poisoned connection.
This suggests that incremental Responses Lite requests can grow a session beyond a limit that applies when the complete history must be sent again. A request-property change then exposes the limit and strands the thread because subsequent requests no longer have a usable incremental anchor. The exact backend constraint may be tokens, serialized bytes, or another full-request validation guard; the client currently discards the error details needed to distinguish them.
What steps can reproduce the bug?
The precise backend threshold is not visible, but the observed reproduction is:
- Start a GPT-5.6 Responses Lite session using the Responses WebSocket.
- Grow the context to roughly 160K–173K estimated tokens while continuing to reuse
previous_response_idincrementally. - Complete another successful turn that adds a substantial tool result. In this case, six GitHub issue bodies added 27,985 serialized characters / 6,972 original tool-result tokens.
- Change reasoning effort, for example from low to high.
- Submit a short prompt.
- Observe the reuse mismatch followed by a full
response.createandRequest blocked. - Retry with a trivial prompt, another GPT-5.6 model, or a fresh WebSocket connection. Observe the same failure.
- Roll back the failed turn—or even roll back the preceding successful turn—then retry. Observe that the full request can remain blocked.
- Compact the thread to a much smaller context and retry. Observe that inference succeeds, including after switching back to GPT-5.6.
A deterministic integration test could use a mocked Responses endpoint with different limits for incremental and full requests, then change reasoning effort after growing history past the full-request limit.
What is the expected behavior?
Changing a request property such as reasoning effort should not make an otherwise valid resumable thread permanently unusable.
If the complete request cannot be accepted after incremental reuse is invalidated, Codex should do at least one of the following:
- compact proactively before the full resend;
- recover automatically after the backend rejects the oversized full request;
- retain an incremental baseline that remains usable when safe;
- surface a specific, actionable error explaining that compaction is required.
Esc rollback should also be able to recover the thread once enough history has been removed, without requiring an unrelated model switch to trigger compaction.
Additional information
The current upstream reuse check explicitly compares reasoning settings:
codex/codex-rs/core/src/client.rs
Lines 306 to 355 in 9e552e9
| fn responses_request_properties_match( | |
| previous: &ResponsesApiRequest, | |
| current: &ResponsesApiRequest, | |
| ) -> bool { | |
| let ResponsesApiRequest { | |
| model: previous_model, | |
| instructions: previous_instructions, | |
| input: _, | |
| tools: previous_tools, | |
| tool_choice: previous_tool_choice, | |
| parallel_tool_calls: previous_parallel_tool_calls, | |
| reasoning: previous_reasoning, | |
| store: previous_store, | |
| stream: previous_stream, | |
| stream_options: _, | |
| include: previous_include, | |
| service_tier: previous_service_tier, | |
| prompt_cache_key: previous_prompt_cache_key, | |
| text: previous_text, | |
| client_metadata: _, | |
| } = previous; | |
| let ResponsesApiRequest { | |
| model: current_model, | |
| instructions: current_instructions, | |
| input: _, | |
| tools: current_tools, | |
| tool_choice: current_tool_choice, | |
| parallel_tool_calls: current_parallel_tool_calls, | |
| reasoning: current_reasoning, | |
| store: current_store, | |
| stream: current_stream, | |
| stream_options: _, | |
| include: current_include, | |
| service_tier: current_service_tier, | |
| prompt_cache_key: current_prompt_cache_key, | |
| text: current_text, | |
| client_metadata: _, | |
| } = current; | |
| previous_model == current_model | |
| && previous_instructions == current_instructions | |
| && previous_tools == current_tools | |
| && previous_tool_choice == current_tool_choice | |
| && previous_parallel_tool_calls == current_parallel_tool_calls | |
| && previous_reasoning == current_reasoning | |
| && previous_store == current_store | |
| && previous_stream == current_stream | |
| // Stream options control delivery for this response, not the context | |
| // referenced by `previous_response_id`. | |
| && previous_include == current_include |
When those properties differ, get_incremental_items() returns None:
codex/codex-rs/core/src/client.rs
Lines 1164 to 1178 in 9e552e9
| /// Checks whether the current request is an incremental extension of the previous request. | |
| /// We only reuse an incremental input delta when non-input request fields are unchanged and | |
| /// `input` is a strict extension of the previous known input. Server-returned output items | |
| /// are treated as part of the baseline so we do not resend them. | |
| fn get_incremental_items( | |
| &self, | |
| request: &ResponsesApiRequest, | |
| last_response: Option<&LastResponse>, | |
| allow_empty_delta: bool, | |
| ) -> Option<Vec<ResponseItem>> { | |
| let previous_request = self.websocket_session.last_request.as_ref()?; | |
| if !responses_request_properties_match(previous_request, request) { | |
| trace!("incremental request failed, websocket reuse properties didn't match"); | |
| return None; | |
| } |
prepare_websocket_request() then falls back to a full ResponseCreate without previous_response_id:
codex/codex-rs/core/src/client.rs
Lines 1222 to 1238 in 9e552e9
| fn prepare_websocket_request( | |
| &mut self, | |
| payload: ResponseCreateWsRequest, | |
| request: &ResponsesApiRequest, | |
| ) -> (ResponsesWsRequest, bool) { | |
| let Some(last_response) = self.get_last_response() else { | |
| return (ResponsesWsRequest::ResponseCreate(payload), false); | |
| }; | |
| let previous_response_id_from_untraced_warmup = | |
| self.websocket_session.last_response_from_untraced_warmup; | |
| let Some(incremental_items) = self.get_incremental_items( | |
| request, | |
| Some(&last_response), | |
| /*allow_empty_delta*/ true, | |
| ) else { | |
| return (ResponsesWsRequest::ResponseCreate(payload), false); | |
| }; |
There is also a separate diagnostic problem. The WebSocket wrapper parses the backend error code and message, but generic mapping requires a status code:
codex/codex-rs/codex-api/src/endpoint/responses_websocket.rs
Lines 580 to 635 in 9e552e9
| struct WrappedWebsocketError { | |
| code: Option<String>, | |
| message: Option<String>, | |
| } | |
| #[derive(Debug, Deserialize)] | |
| struct WrappedWebsocketErrorEvent { | |
| #[serde(rename = "type")] | |
| kind: String, | |
| #[serde(alias = "status_code")] | |
| status: Option<u16>, | |
| #[serde(default)] | |
| error: Option<WrappedWebsocketError>, | |
| #[serde(default)] | |
| headers: Option<JsonMap<String, Value>>, | |
| } | |
| fn parse_wrapped_websocket_error_event(payload: &str) -> Option<WrappedWebsocketErrorEvent> { | |
| let event: WrappedWebsocketErrorEvent = serde_json::from_str(payload).ok()?; | |
| if event.kind != "error" { | |
| return None; | |
| } | |
| Some(event) | |
| } | |
| fn map_wrapped_websocket_error_event( | |
| event: WrappedWebsocketErrorEvent, | |
| original_payload: String, | |
| ) -> Option<ApiError> { | |
| let WrappedWebsocketErrorEvent { | |
| status, | |
| error, | |
| headers, | |
| .. | |
| } = event; | |
| if let Some(error) = error.as_ref() | |
| && let Some(code) = error.code.as_deref() | |
| && code == WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE | |
| { | |
| return Some(ApiError::Retryable { | |
| message: error | |
| .message | |
| .clone() | |
| .unwrap_or_else(|| WEBSOCKET_CONNECTION_LIMIT_REACHED_MESSAGE.to_string()), | |
| delay: None, | |
| }); | |
| } | |
| let status = StatusCode::from_u16(status?).ok()?; | |
| if status.is_success() { | |
| return None; | |
| } | |
| Some(ApiError::Transport(TransportError::Http { | |
| status, |
For this failure, the top-level event had no mappable status, so it fell through to the generic stream parser. That path logs only the event kind:
codex/codex-rs/codex-api/src/sse/responses.rs
Lines 466 to 468 in 9e552e9
| _ => { | |
| trace!("unhandled responses event: {}", event.kind); | |
| } |
As a result, the raw backend code/message that could identify the actual constraint is lost, leaving only Request blocked. in the transcript and logs.
Suggested implementation direction:
- Preserve and log/surface the structured top-level WebSocket error even when it has no HTTP status.
- Before falling back from an incremental request to a complete-history
response.create, compare the request against the supported full-request budget and compact when necessary. - Add recovery coverage for a reuse-property change after a long incremental session, including rollback and retry behavior.
Activity
github-actions commentedon Jul 12, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
ignatremizov commentedon Jul 12, 2026
I reviewed #32177 and its follow-up comments. It likely belongs to the same broader GPT-5.6 / Responses Lite failure family, especially because it reports:
continue;#32028 also appears related: it reports
Request blocked.during remote compaction, with a CLI commenter describing the same GPT-5.5 compaction workaround.I am leaving this issue open for now because it isolates a more specific client transition and includes rollout/source evidence that the earlier reports do not:
previous_response_idrequests;response.create;error/Request blocked.response;So #32177 may ultimately be the same root cause, but its original attachment-focused reproduction does not establish the incremental-to-full-request transition. Maintainers can merge these if backend diagnostics confirm that both full requests are hitting the same guard.
myleswortham commentedon Jul 12, 2026
I had commented in here: #32177, but wanted to mention here as well that I have successfully recreated this issue on command 3/3 times from a new chat with the same prompt, all ending in ~2 minutes of work, and when I asked another to investigate, it ended in 10 minutes, with the same Request blocked failure. Again, brand new chat, single prompt, all ending in failure. The output from the investigative agent was:
Worked for 9m 41s
Yeah, this is not a reasonable “it took a while” failure—the run is being killed at the command boundary mid-investigation. I’m going to identify the exact rejected tool calls from the local Codex session data, not guess from the screenshot. I’m also using the OpenAI/Codex documentation skill because this is specifically about Codex’s own execution behavior.
Ran a command
The skill’s normal manual cache is intentionally skipped because this session is explicitly read-only. I’m splitting the check now: one trace for the exact failed conversation/tool payloads, one for the UI error’s source/meaning, and one for the configured sandbox/policy layer. No builds and no repository edits.
Trace blocked callsUi error sourcePolicy configfinished
Ran commands
I’ve found the recurring trigger. It is not a timeout: the same investigation repeatedly emits Unix-style filename globs such as chat., chat-.cpp, and chat-auto-parser* as literal Windows path arguments. rg rejects them with Windows error 123; because those searches are wrapped in Promise.all, one rejected subcommand makes the whole command card fail. I’m checking why the UI renames that ordinary command failure to “Request blocked” instead of showing the real error.
Ran commands
The bad rg globs are real command mistakes, but they are not the thing killing the run—I’m correcting that diagnosis. The fatal pattern is later: subagent results arrive, the parent thread makes its next model request, and that request returns no assistant message. Your trace then records task_complete with last_agent_message: null, while the collaboration layer reports Request blocked. This is an alpha multi-agent handoff failure.
Searched the webSearched the web for multi_agent feature config.toml subagents | developers.openai.com ...
Searched the web for "multi_agent" | developers.openai.com ...
Searched the web for https://developers.openai.com/codex/config-reference
Searched the web for 'multi_agent'
Searched the web for 'How subagents work'
Request blocked.
cuipengfei commentedon Jul 12, 2026
Cross-referencing related reports that may share the same backend root cause.
Request blockedongpt-5.6-sol;gpt-5.5unaffected.Request blockedon allgpt-5.6models.<|channel|>analysisis sufficient to triggerRequest blocked/invalid_request_bodyon allgpt-5.6-*models, whilegpt-5.5and earlier accept it.Your analysis of the incremental → full-history
response.createtransition is valuable. The full-history request would contain all prior context as input text — if that context includes Harmony control-token markers (e.g.<|channel|>analysis), thegpt-5.6safety classifier may reject it at the input level, independent of payload size.Additionally, @myleswortham's investigation found that the fatal pattern occurs when "subagent results arrive, the parent thread makes its next model request, and that request returns no assistant message" — described as an "alpha multi-agent handoff failure." This is consistent with the Harmony control-token hypothesis: if subagent results contain
<|channel|>analysismarkers (from assistant reasoning/thinking serialized via Harmony dialect), the parent thread's next request — which includes those results in its input payload — would be rejected by thegpt-5.6input-side safety classifier.OpenAI's Safety Checks documentation confirms that GPT-5+ classifies requests (including input) into risk thresholds, and repeated high-risk classifications can lead to errors and eventual account suspension — which matches the
feature_flag_blockedescalation path reported in oh-my-pi#5184.It may be worth testing whether your failing full-history payloads contain
<|channel|>analysisor similar control-token text.hogeheer499-commits commentedon Jul 25, 2026
Additional evidence from three affected local Codex rollouts: the original task and two derived/forked tasks.
<|channel|>analysisoccurs once in each affected rollout, inside acustom_tool_call_output, before the block. This is consistent with the marker hypothesis discussed above, although it does not by itself prove that this is the only backend trigger.full_context_window_limit_reachedandtoken_limit_reachedwere false.type: "error"→unhandled responses event: error→Turn error: Request blocked.The nested backend error code/message is not retained.I also checked current
main(20dafe201d91).responses_websocket.rsparses the wrapper, but generic mapping still requires a top-level status. A status-less error therefore falls through to the generic Responses parser, where only the event kind is logged.I have a focused local proof patch ready, but have not opened a PR because contributions are invitation-only. It:
error.type,error.code, anderror.messagefor status-less top-level WebSocket errors;invalid_request_body: Request blocked.reaches the caller.Validation completed:
codex-apitest suite: 155 tests across its unit/integration binaries, all passing;codex-coreWebSocket regression: passing;codex-apiClippy with all targets/features and-D warnings: passing;git diff --check: clean.If the team considers this client-side diagnostic/recovery work a current priority, does mapping a status-less terminal error to
ApiError::InvalidRequestmatch the intended direction, or would you prefer a more specific variant? I would be happy to submit the focused patch and adapt the classification if invited.nos1609 commentedon Aug 4, 2026
I reproduced what appears to be the same failure today on another long-running thread.
Affected thread ID:
019f43ac-20b8-7ad1-965b-3b2e6460255dClient: Codex CLI
0.146.0on LinuxThe thread contained ordinary engineering work on a multi-provider adapter involving GPT-5.6 and gpt-oss on Cerebras. It contained no credentials or safety-sensitive material.
Five consecutive turns failed with:
Request blocked.The diagnostic sequence differs slightly from the original report, but reaches the same full-request failure path:
incremental request failed, items didn't matchunhandled responses event: errorTurn error: Request blocked.The rollout contained 468,645 items and a large multi-agent history. Authentication remained valid, model discovery returned HTTP 200, and the Responses WebSocket connected successfully. This was not a login or general connectivity failure.
I recovered the thread only after restarting Codex, switching to GPT-5.5, and then switching back to GPT-5.6 Sol. A compaction occurred during this recovery, after which GPT-5.6 requests succeeded again.
This workaround is not acceptable for a long-running thread on a top-tier paid subscription. Codex should compact automatically before a full resend, preserve a usable incremental baseline, or expose the actual backend constraint with a direct recovery action. A failed request must not strand an otherwise valid thread.
frisco-deng commentedon Aug 6, 2026
I am getting similar issues and incorrect or unexplained flags. At first I thought it was triggered from the prompt, I can not pin down why I am getting random ```Request Blocked`` no traceback reason.
Replayed the exact rejected prompt in a clean, ephemeral
gpt-5.6-solsession using the same authentication. It succeeded and generated a complete plan.What happened in
019fd46e-708f-7f61-a648-6660000a6b6eThere were zero compaction. Stored diagnostics contain no moderation decision, Guardian intervention, HTTP 401/403/429, Cloudflare challenge, authentication failure, or rate-limit failure.
The likely trigger is:
211k-token incremental session
+
Default → Plan mode
↓
instructions/tool schema changed
↓
incremental baseline could not be reused
↓
full 211k+ history request
↓
opaque backend request-limit rejection
This is the same failure class as openai/codex #32533, where changing reasoning settings invalidates reuse and strands a large thread. Issue #32177 independently reports the same 3–5 second opaque block with substantial histories.
WoojinAhn commentedon Aug 8, 2026
I reproduced what appears to be the same terminal failure path on 2026-08-08.
Environment:
0.147.0gpt-5.6-sol,xhighThe sequence immediately before the failure was:
The preceding sampling completed successfully with approximately 234K input tokens in a 258.4K context window. The context-window limit was not marked as reached.
A child-agent research report was appended as new model-visible input. It discussed existing
Request blocked.reports and referenced a known Harmony analysis-channel control marker. I am intentionally omitting the literal marker.The next Responses WebSocket request authenticated successfully and received
response.in_progress.Approximately three seconds later, the server emitted a top-level
error.Codex logged:
unhandled responses event: errorTurn error: Request blocked.The final turn record contained
codex_error_info: other, with no HTTP status, request ID, policy code, oradditionalDetails.Authentication, provider reachability, state databases, and the WebSocket handshake were healthy. Rate limits were not reached, and this was not a sandbox or approval denial.
The literal marker alone does not appear sufficient: related material existed earlier in tool-call data while several samplings still succeeded. Message role, child-agent handoff, or full-history re-evaluation may be relevant.
During the investigation, derived research sessions also failed with the same error. This suggests that a child-agent result or inherited history can propagate the failure into another session.
This adds three observations to the original report:
0.147.0;other.This may overlap with the safety/history-poisoning reports in #32177 and #35160. Preserving the nested backend error code and request ID would help distinguish a request-size/history validation failure from a safety-classifier false positive.
I can provide the affected session identifiers and sanitized diagnostics privately if maintainers specify an appropriate channel.
shirokuma-xv commentedon Aug 11, 2026
I reproduced a closely related failure on Codex Desktop for macOS.
Affected thread:
019f72e8-2d3a-75b3-9612-649417bb90e6The task was a long-running Goal (approximately 18 hours) with 33 completed subagents. The observed sequence was:
stream disconnected before completion: websocket closed by server before response.completedReconnecting... 5/5Request blocked.blockedI also reran the work in a newly created worktree, but the same failure occurred there. This suggests the issue is not specific to the original checkout, repository files, or a local worktree-only state.
Additional observations:
continueproduce no assistant output in the affected thread;blockedafter attempting to resume it;This appears related to the long-history / full-resend / child-agent handoff failure described here, and also resembles #35160 and #24475. A screenshot showing the 5/5 reconnect state, four
Request blocked.messages, the blocked Goal, and 33 completed subagents is available.Is there a supported recovery path that preserves the existing Goal and thread?