Skip to content

Responses Lite: changing reasoning effort can strand a large session on “Request blocked.” until compaction #32533

Open

Description

@ignatremizov

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:

  1. Start a GPT-5.6 Responses Lite session using the Responses WebSocket.
  2. Grow the context to roughly 160K–173K estimated tokens while continuing to reuse previous_response_id incrementally.
  3. 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.
  4. Change reasoning effort, for example from low to high.
  5. Submit a short prompt.
  6. Observe the reuse mismatch followed by a full response.create and Request blocked.
  7. Retry with a trivial prompt, another GPT-5.6 model, or a fresh WebSocket connection. Observe the same failure.
  8. Roll back the failed turn—or even roll back the preceding successful turn—then retry. Observe that the full request can remain blocked.
  9. 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:

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:

/// 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:

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:

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:

_ => {
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:

  1. Preserve and log/surface the structured top-level WebSocket error even when it has no HTTP status.
  2. 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.
  3. Add recovery coverage for a reuse-property change after a long incremental session, including rollback and retry behavior.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    contextIssues related to context management (including compaction)
    connectivityIssues involving networking or endpoint connectivity problems (disconnections)
    on Jul 12, 2026
  2. github-actions commented on Jul 12, 2026

    @github-actions
    Contributor

    Potential duplicates detected. Please review them and close your issue if it is a duplicate.

    Powered by Codex Action

  3. ignatremizov commented on Jul 12, 2026

    @ignatremizov
    Author

    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:

    • a long-running thread becoming persistently blocked on short prompts such as continue;
    • GPT-5.5 continuing to work;
    • switching to GPT-5.5, compacting, and then returning to GPT-5.6 as a workaround.

    #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:

    1. the thread succeeds at approximately 172K estimated tokens while using incremental previous_response_id requests;
    2. changing reasoning effort makes the WebSocket reuse properties differ;
    3. Codex consequently falls back to a complete-history response.create;
    4. that full request receives the top-level error / Request blocked. response;
    5. rollback does not restore an incremental anchor, while compaction to approximately 36K tokens recovers the thread;
    6. the status-less WebSocket error currently loses its structured backend code/message in the client.

    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.

  4. myleswortham commented on Jul 12, 2026

    @myleswortham

    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.

  5. cuipengfei commented on Jul 12, 2026

    @cuipengfei

    Cross-referencing related reports that may share the same backend root cause.

    Your analysis of the incremental → full-history response.create transition 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), the gpt-5.6 safety 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|>analysis markers (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 the gpt-5.6 input-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_blocked escalation path reported in oh-my-pi#5184.

    It may be worth testing whether your failing full-history payloads contain <|channel|>analysis or similar control-token text.

  6. hogeheer499-commits commented on Jul 25, 2026

    @hogeheer499-commits

    Additional evidence from three affected local Codex rollouts: the original task and two derived/forked tasks.

    • The exact text <|channel|>analysis occurs once in each affected rollout, inside a custom_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.
    • In the clearest run, the last recorded usage was 140,173 total tokens / 128,116 estimated context tokens. The configured auto-compaction limit was 244,800, and both full_context_window_limit_reached and token_limit_reached were false.
    • The terminal sequence is type: "error" → unhandled responses event: error → Turn error: Request blocked. The nested backend error code/message is not retained.
    • Forking did not recover the work: the derived task retained the same tool output and later failed too. That makes a fork an unreliable workaround for this failure mode.

    I also checked current main (20dafe201d91). responses_websocket.rs parses 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:

    1. preserves nested error.type, error.code, and error.message for status-less top-level WebSocket errors;
    2. treats the event as terminal instead of retrying or silently discarding it;
    3. adds two mapper unit tests and an end-to-end WebSocket regression test proving invalid_request_body: Request blocked. reaches the caller.

    Validation completed:

    • full codex-api test suite: 155 tests across its unit/integration binaries, all passing;
    • focused codex-core WebSocket regression: passing;
    • codex-api Clippy with all targets/features and -D warnings: passing;
    • formatting and 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::InvalidRequest match 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.

  7. nos1609 commented on Aug 4, 2026

    @nos1609

    I reproduced what appears to be the same failure today on another long-running thread.

    Affected thread ID: 019f43ac-20b8-7ad1-965b-3b2e6460255d
    Client: Codex CLI 0.146.0 on Linux

    The 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 match
    unhandled responses event: error
    Turn 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.

  8. frisco-deng commented on Aug 6, 2026

    @frisco-deng

    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-sol session using the same authentication. It succeeded and generated a complete plan.

    What happened in 019fd46e-708f-7f61-a648-6660000a6b6e

    Time UTC Event
    00:18–00:31 One successful turn ran 35 model/tool cycles and grew from 18,632 to 211,593 tokens.
    00:31:05 Turn completed normally.
    01:10:37 Session changed from Default to Plan mode.
    01:10:43 Your ordinary planning prompt was submitted.
    01:10:44 Backend reported response.in_progress.
    01:10:47 Backend emitted an opaque error; client displayed Request blocked.

    There 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.

  9. WoojinAhn commented on Aug 8, 2026

    @WoojinAhn

    I reproduced what appears to be the same terminal failure path on 2026-08-08.

    Environment:

    • Codex CLI 0.147.0
    • macOS 26.6, arm64
    • gpt-5.6-sol, xhigh

    The sequence immediately before the failure was:

    1. 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.

    2. 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.

    3. The next Responses WebSocket request authenticated successfully and received response.in_progress.

    4. Approximately three seconds later, the server emitted a top-level error.

    5. Codex logged:

      unhandled responses event: error
      Turn error: Request blocked.

    6. The final turn record contained codex_error_info: other, with no HTTP status, request ID, policy code, or additionalDetails.

    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:

    • the failure still occurs on CLI 0.147.0;
    • a child-agent result/history handoff can expose it without changing reasoning effort;
    • the status-less WebSocket error still loses the backend classification and is surfaced only as 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.

  10. shirokuma-xv commented on Aug 11, 2026

    @shirokuma-xv

    I reproduced a closely related failure on Codex Desktop for macOS.

    Affected thread:
    019f72e8-2d3a-75b3-9612-649417bb90e6

    The 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.completed
    • Reconnecting... 5/5
    • repeated Request blocked.
    • the Goal UI became blocked

    I 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:

    • the current Codex thread works normally with the same account and machine;
    • short follow-ups such as continue produce no assistant output in the affected thread;
    • recovery turns are recorded as completed with no assistant message;
    • the local Goal state returns to blocked after attempting to resume it;
    • local diagnostics did not show a corresponding HTTP 401/403/429, authentication failure, or rate-limit exhaustion;
    • the affected rollout is approximately 1.16 GB.

    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?

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

    CLIIssues related to the Codex CLIbugSomething isn't workingconnectivityIssues involving networking or endpoint connectivity problems (disconnections)contextIssues related to context management (including compaction)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions