Skip to content

[macOS Desktop] Automatic compaction stalls with WebSocket idle timeout; persists after app restart #42896

Open

Description

@omarpinarecords

What version of the Codex App are you using?

Installed desktop bundle inspected after the incident: 26.901.41123 (build 7942), bundle identifier com.openai.codex. The installed application is named ChatGPT and the affected surface is its Codex task UI. This is installed-bundle evidence, not a separate confirmation of the build loaded at the start of the affected turn.

What subscription do you have?

Not included in this report.

What platform is your computer?

macOS / Darwin 25.6.0, arm64.

What issue are you seeing?

A long-running Codex task remains in automatic context compaction and reconnect attempts without delivering further task output. I waited more than twenty minutes and restarted the desktop app, but the problem persisted. The waiting duration and unsuccessful restart are my observations, not a controlled timing measurement.

The visible activity sequence was:

Ran command
Success
Context automatically compacting
Reconnecting 2/2
stream disconnected before completion: idle timeout waiting for websocket

The preceding shell command was shown as successful. The connection error appeared afterward, while the UI showed automatic context compaction. This does not establish that the shell command or any particular script caused the failure.

A read-only native task status check during the incident still returned active / inProgress with a null terminal error and no new message or tool marker in the latest attempt. This conflicts with using the running indicator as evidence of productive progress; it does not establish that execution has recovered.

I also opened Voice in the affected task and it appeared to be waiting. I have not established whether Voice is relevant to the underlying failure.

What steps can reproduce the bug?

Observed sequence, not a deterministic minimal reproduction:

  1. Run a long Codex task until automatic context compaction starts.
  2. Observe prolonged compaction followed by reconnect attempts and the WebSocket idle-timeout error above.
  3. Wait for recovery; no further task result was confirmed during the observation.
  4. Restart the desktop app and reopen the same task; the problem remains visible.

The screenshot documenting the error was taken September 4, 2026 at approximately 20:33 EDT (September 5 at 00:33 UTC). It is intentionally not attached because it contains private task content.

The task composer displayed GPT-6 Astra / Max. That is a UI observation, not independent runtime attribution or evidence that the model caused the failure.

Troubleshooting attempted after the initial report

After restarting the desktop app did not resolve the visible stall, I tried the following in the affected task:

  1. Used Clear on the Goal.
  2. Used Stop on the running task.
  3. Requested manual context compaction to see whether it could complete.

I still do not see progress after these steps. This is my current observation; successful manual compaction or recovery has not been confirmed.

Read-only native inspection confirmed that the preceding attempt became interrupted. A subsequent attempt appears as inProgress, with a null terminal error and no latest assistant message or tool marker exposed in the status snapshot. The most recent check was at 2026-09-04 20:50:23 EDT (UTC-0400) / 2026-09-05 00:50:23 UTC. These fields do not independently confirm that Goal clearance succeeded, that the later attempt is the manual compaction operation, or that useful execution has resumed.

The inspection did not restart or resume the stopped drafting task. The unresolved issue is whether compaction can finish and provide an accurate, actionable recovery state after these manual controls.

What is the expected behavior?

Automatic compaction should complete and continue the existing task, or report a clear recoverable failure. If a stream times out, the UI should distinguish actual execution from reconnecting or failed recovery and expose a supported way to continue the same task without losing its completed work or duplicating actions.

Uploaded performance trace

The app displayed Performance trace uploaded and supplied this Feedback ID:

01a06eb8-7aa2-7981-a85f-9f95ea5da9f4

Please correlate the uploaded trace with the compaction and reconnection interval. The upload confirmation does not establish that engineering has reviewed the trace.

Related reports and diagnostic limits

This report adds the current compaction-stage stall, persistence after my app restart, native status discrepancy, and uploaded trace. A shared root cause with those reports is not established.

OpenAI Status displayed fully operational when checked around 20:34 EDT. No general outage, network cause, or model-specific regression is asserted.

No private documents, task prompts, local paths, credentials, raw logs, or screenshots are attached.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    contextIssues related to context management (including compaction)
    connectivityIssues involving networking or endpoint connectivity problems (disconnections)
    on Sep 5, 2026
  2. axeronta commented on Sep 5, 2026

    @axeronta

    Additional Windows incident: stalled compaction/reconnection and failed same-task recovery

    Reporting a separate Windows incident on 5 September 2026. This adds platform/version details, measured interrupted-turn durations, recorded context usage, and a successful fresh-task recovery. A shared underlying cause with the original macOS report is not established.

    Environment

    • Codex desktop app, installed Windows package version 26.901.5280.0 (package inspection; not copied from the About dialog).
    • Session metadata records Codex runtime 0.153.0, originator Codex Desktop.
    • Windows 11 Home x64, OS 10.0.26200.
    • ChatGPT Pro.
    • Affected task used gpt-6-astra; the final recovery attempt retained xhigh reasoning. No model-specific cause is asserted.

    Observed failure

    A long, tool-heavy local task stopped making visible progress. The UI repeatedly showed:

    Context automatically compacting
    Reconnecting 2/2
    

    After interruptions and further prompts, the task continued to appear working without returning a useful response. One recovery run initially acknowledged the request and completed a shell command, then stopped progressing around the compaction/reconnection stage.

    A read-only task inspection initially found it idle with recent turns marked interrupted. A deliberately small recovery prompt was then submitted through the app's task messaging interface: give a short acknowledgement, check that one saved checkpoint exists, and stop without restarting the long investigation. The request was accepted, but subsequent snapshots showed active / inProgress, error: null, and no latest assistant message or tool marker.

    Measured timeline (UTC, 2026-09-05)

    Start End/status Recorded turn duration
    09:04:18 Interrupted at 09:16:45 746,322 ms (12m 26s)
    09:16:49 Interrupted at 09:19:25 156,098 ms (2m 36s)
    09:22:09 Interrupted at 09:37:13 904,243 ms (15m 4s)
    09:37:42 Interrupted at 09:38:59 76,963 ms (1m 17s)
    09:40:10 Minimal recovery request accepted; still no message/tool activity at the final recovery check several minutes later No completion recorded at that check

    These are turn durations, not measurements of time spent exclusively in compaction. The long productive investigation before this interval is intentionally excluded.

    The local app continued answering task read/list/status requests in milliseconds during the stall. A shell read could independently access the saved project checkpoints. Those facts distinguish a nonresponsive task from a confirmed whole-app or filesystem freeze; they do not diagnose the remote service.

    Context and usage concern

    Two recorded token-count events near the failure were:

    Timestamp UTC Input tokens Cached input tokens Output tokens
    09:04:57 586,628 586,368 575
    09:22:29 587,404 587,136 358

    The session reported model_context_window: 828400. The local rollout was about 27 MB at inspection.

    The user reports substantial wasted time and tokens from repeated failed recovery and reconstruction. The values above are recorded request token fields, not a calculation of wasted or billable usage. Cached input is a subset of input and must not be double-counted. The failed compaction attempts' actual accounting is not visible in these observations. Please investigate server-side retry/compaction usage attribution and whether affected usage can be restored under the applicable support policy.

    Recovery attempted and result

    1. Interrupted/retried the affected task repeatedly.
    2. Sent the minimal recovery check described above; it was accepted but produced no output during observation.
    3. Created a fresh task with a compact handoff referencing the existing saved files, without copying the long conversation.
    4. The fresh task promptly produced a visible acknowledgement and completed a read-only file check, then began recovering the saved work.

    The new task responding is a verified recovery of the workflow, not evidence that the original task was repaired. No transcript/database edits or project deletion were needed.

    Expected behavior

    Compaction should complete or produce a bounded, actionable failure. A task that cannot recover should expose a clear state and a supported way to continue from saved work. The UI should distinguish actual task progress from compaction/reconnection waiting, and failed retries should have transparent usage accounting.

    Correlation and privacy

    Affected local task ID: 01a06f59-bdbe-7543-8e20-68809e1defdd.

    Minimal recovery turn ID: 01a070f0-984c-7001-8c75-632fe64b25c0.

    These are local task/turn identifiers, not an uploaded feedback or performance-trace ID. No trace upload or internal support case creation is claimed. Private project text, local usernames/paths, screenshots, raw session logs, and credentials are omitted.

  3. easyvibecoding commented on Sep 5, 2026

    @easyvibecoding

    Additional evidence: compaction retry amplification in 0.153.4, spontaneous HTTP recovery, and a no-subagent protocol reproducer

    Adding a separate macOS observation and controlled reproduction to this issue rather than opening a duplicate. The observation overlaps this report; it does not establish that every incident in this thread has the same upstream cause.

    Environment

    • Installed Codex desktop bundle: 26.901.41600 (7982).
    • Bundled executable reports codex-cli 0.153.4; the protocol experiments below use that actual executable.
    • macOS 26.6.2, arm64; ChatGPT authentication; model gpt-6-astra. No plan/account identifiers are included.
    • Source inspected: release tag rust-v0.153.4, commit 3d2ee51ca2d5db578f328aa75e20aa22c0197c9a. This was also the latest stable GitHub release when checked for this report.

    Observed recovery timeline, with identifying information removed

    A tool-heavy task entered run_auto_compact{reason=ContextLimit phase=MidTurn}. The task initially looked stuck while subagent completion notifications continued. It later recovered without interruption, another prompt, configuration changes, or app restart during this incident.

    Times below are relative to the last pre-compaction model token-count event; this baseline is not a direct measurement of server inference start.

    Relative time Observed event
    T+0s Last model event; automatic compaction starts and opens a fresh WS connection
    T+305s idle timeout waiting for websocket; compaction retry 1/2
    T+610s Same error; compaction retry 2/2
    T+919s After a third WS attempt, falling back to HTTP
    T+921s HTTP response headers: 200 OK; compaction is still pending
    T+1255.218s A successful compacted record is persisted; window number advances
    T+1260s onward Queued agent messages are consumed and ordinary task/tool execution resumes

    Sanitized log fields:

    run_auto_compact{reason=ContextLimit phase=MidTurn}
    remote compaction v2 stream failed; retrying request after delay
    retries=1 max_retries=2
    compact_error=stream disconnected before completion: idle timeout waiting for websocket
    
    remote compaction v2 stream failed; retrying request after delay
    retries=2 max_retries=2
    compact_error=stream disconnected before completion: idle timeout waiting for websocket
    
    falling back to HTTP
    transport="responses_http" status=200
    [Later: successful compaction record and subsequent tool execution]
    

    The successful compaction records input_tokens=205015, cached_input_tokens=204928, output_tokens=8708. The HTTP request-to-compaction-completion interval was approximately 336.7s. These are recorded successful-request values, not a calculation of failed-request or billable usage.

    For comparison, the same task's three earlier successful compactions took approximately 190.8s, 219.0s, and 295.8s from the preceding model event, producing 5,202, 6,140, and 7,697 output tokens. Increasing summary generation time crossing the 300s idle threshold is a plausible explanation, but those intervals include client/routing work and are not server-side inference timings.

    Controlled experiment: no subagents required

    I reproduced the client failure/retry/fallback mechanism with the installed 0.153.4 binary, a loopback-only synthetic HTTP/WS server, fresh temporary Codex homes, ephemeral test threads, and no subagents or copied credentials. No real conversation is replayed upstream.

    The fixture uses thread/compact/start to reach the same remote-compaction-v2 streaming/retry code. It does not reproduce the entire production automatic ContextLimit trigger or its payload. To make the experiment fast, the idle timeout is 1s; the slow fixture takes 1.6s to produce a valid compaction item and response.completed. HTTP/SSE sends a small application event every 150ms while waiting. WS either stays silent, sends the same JSON event, or sends native protocol Ping frames.

    Observed results from the exact publicly included script:

    Fixture Compaction elapsed (s) Compaction request sequence
    Fast completion, no heartbeat 0.306 WS
    Slow completion, silent WS 5.542 WS → WS → WS → HTTP
    Slow completion, JSON heartbeat 1.667 WS
    Slow completion, direct HTTP/SSE 1.665 HTTP
    Slow completion, native WS Ping only 5.547 WS → WS → WS → HTTP

    All five cases completed and all sequence assertions passed. The native-Ping case sent 21 protocol Ping frames across its WS attempts and still followed the three-WS-then-HTTP path. The silent and native-Ping cases emit:

    Reconnecting... 2/2
    stream disconnected before completion: idle timeout waiting for websocket
    Falling back from WebSockets to HTTPS transport.
    

    Thus a JSON application heartbeat and a WebSocket protocol Ping are not equivalent for this watchdog. The HTTP comparison includes application events; it does not show that HTTP can wait indefinitely with no events.

    Source-level explanation

    Subagent notifications are queued by session/handlers.rs:81 and session/input_queue.rs:122. The compaction request uses a cloned history/fixed prompt. The production queued messages were consumed after compaction succeeded. Together with the no-subagent fixture, this means concurrent subagents are not necessary to reproduce the retry amplification; it does not exclude workload size or backend concurrency as contributing factors in a real task.

    Possible fixes to evaluate

    1. Make liveness visible to the layer enforcing the watchdog. Emit an application-level compaction progress/heartbeat event while work is pending, or propagate transport-liveness information into a distinct liveness clock. Adding native Ping alone does not address the demonstrated upper-layer timeout.
    2. Separate connection health from model/compaction progress. A live socket is not proof of useful inference progress, while lack of text/output is not proof of a dead socket. Retain a bounded overall/no-progress policy and cancellation support so arbitrary heartbeats cannot hide a genuinely stuck backend indefinitely.
    3. Evaluate compaction-specific earlier HTTP fallback after an idle timeout, instead of necessarily repeating the full 300s wait three times. Use a typed error reason rather than matching human-readable strings; do not apply a shorter generic timeout to all high-reasoning sampling. Ensure the prior response is cancelled or deduplicated before a replacement attempt, and preserve transparent usage accounting.
    4. Show the actual recovery phase. Distinguish waiting for compaction, WS retry, and HTTP fallback, with elapsed time/last meaningful event. A displayed 2/2 should not imply that no further recovery phase remains.

    Suggested regression coverage: slow-but-live compaction with application heartbeats; native Ping/Pong without application events; truly silent/dead streams; compaction-specific fallback; cancellation; no duplicate/partially installed compaction results; and subagent messages arriving during the wait without loss or replay.

    These are proposed fixes, not a production patch or a claim that forcing HTTP permanently fixes every related stall. In particular, the synthetic custom-provider configuration below is not a claim that the desktop's built-in openai provider supports the same user override.

    Real incident correlation identifiers

    Included with the reporter's explicit authorization to help server-side correlation:

    • Task/thread ID: 01a070b1-ed7c-76e2-bc69-4a4f4fac6e59
    • Turn ID: 01a070b1-eee3-7570-aa8e-03768fb659b6
    • Successful compaction response ID: resp_009d44e1b8826573016a9c03882e5487d0a97be3306a94573c

    These are task/turn/response identifiers, not uploaded feedback or performance-trace IDs. No private conversation or trace upload is being shared by this comment.

    Limits and privacy

    The real incident's upstream reason for the long gap remains unconfirmed: slow generation without surfaced heartbeat, a proxy/transport delivery problem, and related service behavior need server-side request traces to distinguish. No raw wire trace was available. This report establishes the client mechanism and actual later recovery, not a definitive diagnosis of OpenAI's internal service.

    Other than the three explicitly authorized correlation identifiers above, the report omits runtime identifiers, CF-Ray values, exact incident timestamps, usernames, project names, local paths, conversation content, screenshots, complete logs, account identifiers, and credentials. Only relative timings, relevant versions, selected diagnostic fields, aggregate token counts, and synthetic reproduction code/results are included. No private trace upload or support-case creation is claimed.

    Related: #39771 discusses the distinction between model silence and protocol liveness; #38638 discusses retry latency and transport fallback. This adds compaction-v2-specific evidence and a fast, isolated reproducer on 0.153.4.

    Standalone synthetic reproducer (Python; five cases and assertions)

    Requirements: Python 3.10+, aiohttp, and zstandard. Run in a disposable local test directory. Put the desired codex executable on PATH or set CODEX_BINARY to it. The script starts only a loopback model endpoint and uses fresh temporary Codex state; it never copies an auth file or credential environment variable.

    python protocol_probe.py
    """Isolated protocol experiment against the installed Codex binary.
    
    Only synthetic input, loopback server, fresh temporary Codex home, no credentials.
    This tests client retry mechanics, not the production server's underlying cause.
    """
    import asyncio
    import json
    import os
    import shutil
    from pathlib import Path
    import tempfile
    import time
    
    from aiohttp import web, WSMsgType
    import zstandard
    
    ROOT = Path(tempfile.mkdtemp(prefix='codex-compaction-probe-'))
    BIN = os.environ.get('CODEX_BINARY') or shutil.which('codex')
    if not BIN:
        raise SystemExit('Set CODEX_BINARY to the installed Codex executable, or put codex on PATH.')
    
    
    def events(compact):
        item = ({'type': 'compaction', 'encrypted_content': 'SYNTHETIC_TEST_SUMMARY'}
                if compact else {'type': 'message', 'role': 'assistant', 'id': 'msg-test',
                                 'content': [{'type': 'output_text', 'text': 'fixture ready'}]})
        return [
            {'type': 'response.output_item.done', 'item': item},
            {'type': 'response.completed', 'response': {'id': 'resp-test-compact' if compact else 'resp-test-seed',
              'usage': {'input_tokens': 100, 'output_tokens': 10, 'total_tokens': 110}}},
        ]
    
    
    async def scenario(name, delay, ws_heartbeat, websocket_enabled=True, protocol_ping=False):
        requests, notices, server_errors = [], [], []
        ping_count = 0
        started = time.monotonic()
    
        async def respond(send, body, transport, ping=None):
            nonlocal ping_count
            compact = any(x.get('type') == 'compaction_trigger' for x in body.get('input', []))
            requests.append({'at': round(time.monotonic()-started, 3), 'transport': transport, 'compact': compact})
            if compact:
                end = time.monotonic() + delay
                while time.monotonic() < end:
                    if transport == 'http' or ws_heartbeat:
                        await send({'type': 'fixture.keepalive'})
                    elif protocol_ping and ping is not None:
                        await ping(b'fixture')
                        ping_count += 1
                    await asyncio.sleep(0.15)
            for event in events(compact):
                await send(event)
    
        async def handler(req):
            if req.method == 'GET' and req.path.endswith('/models'):
                return web.json_response({'models': []})
            ws = web.WebSocketResponse()
            if ws.can_prepare(req).ok:
                await ws.prepare(req)
                async for msg in ws:
                    if msg.type == WSMsgType.TEXT:
                        try:
                            await respond(ws.send_json, json.loads(msg.data), 'ws', ws.ping)
                        except Exception as exc:
                            server_errors.append(type(exc).__name__)
                return ws
            data = await req.read()
            if data and not data.lstrip().startswith(b'{') and req.headers.get('Content-Encoding') == 'zstd':
                data = zstandard.ZstdDecompressor().decompress(data)
            try:
                body = json.loads(data)
            except Exception:
                return web.json_response({'error': 'fixture only supports JSON'}, status=400)
            response = web.StreamResponse(headers={'Content-Type': 'text/event-stream'})
            await response.prepare(req)
            async def send(event):
                await response.write(('data: '+json.dumps(event)+'\n\n').encode())
            try:
                await respond(send, body, 'http')
                await response.write_eof()
            except Exception as exc:
                server_errors.append(type(exc).__name__)
            return response
    
        app = web.Application()
        app.router.add_route('*', '/{path:.*}', handler)
        runner = web.AppRunner(app)
        await runner.setup()
        site = web.TCPSite(runner, '127.0.0.1', 0)
        await site.start()
        port = site._server.sockets[0].getsockname()[1]
        probe_home = Path(tempfile.mkdtemp(prefix=name+'-', dir=ROOT))
        (probe_home/'config.toml').write_text(f'''
    model = "gpt-6-astra"
    model_provider = "fixture"
    model_context_window = 258400
    model_auto_compact_token_limit = 250000
    [features]
    remote_compaction_v2 = true
    responses_websockets = true
    responses_websockets_v2 = true
    [analytics]
    enabled = false
    [model_providers.fixture]
    name = "OpenAI"
    base_url = "http://127.0.0.1:{port}/v1"
    wire_api = "responses"
    requires_openai_auth = false
    supports_websockets = {str(websocket_enabled).lower()}
    stream_idle_timeout_ms = 1000
    stream_max_retries = 2
    request_max_retries = 0
    ''')
        # Whitelist inherited variables. No auth file or credential variable is copied.
        env = {k: os.environ[k] for k in ['PATH', 'HOME', 'TMPDIR', 'LANG'] if k in os.environ}
        env['CODEX_HOME'] = str(probe_home)
        proc = await asyncio.create_subprocess_exec(BIN, 'app-server', '--stdio', cwd=probe_home,
            env=env, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
        errors = []
        queue = asyncio.Queue()
        async def read_stdout():
            while line := await proc.stdout.readline():
                try:
                    value = json.loads(line)
                except Exception:
                    continue
                notices.append(value)
                await queue.put(value)
        async def read_stderr():
            while line := await proc.stderr.readline():
                errors.append(line.decode(errors='replace'))
        readers = [asyncio.create_task(read_stdout()), asyncio.create_task(read_stderr())]
        async def send(method, params, id):
            proc.stdin.write((json.dumps({'id': id, 'method': method, 'params': params})+'\n').encode())
            await proc.stdin.drain()
        async def until(predicate, timeout=15):
            async def inner():
                while True:
                    value = await queue.get()
                    if predicate(value):
                        return value
            return await asyncio.wait_for(inner(), timeout)
        result = {'scenario': name, 'delay_seconds': delay, 'ws_heartbeat': ws_heartbeat,
                  'websocket_enabled': websocket_enabled, 'protocol_ping': protocol_ping,
                  'idle_timeout_seconds': 1}
        try:
            await send('initialize', {'clientInfo': {'name': 'compaction_protocol_probe', 'version': '1'},
                                     'capabilities': {'experimentalApi': True}}, 1)
            await until(lambda v: v.get('id') == 1)
            await send('thread/start', {'model': 'gpt-6-astra', 'modelProvider': 'fixture',
                'cwd': str(probe_home), 'ephemeral': True, 'baseInstructions': 'Synthetic protocol test. Reply briefly.'}, 2)
            r = await until(lambda v: v.get('id') == 2)
            if 'error' in r:
                raise RuntimeError(str(r['error']))
            tid = r['result']['thread']['id']
            await send('turn/start', {'threadId': tid, 'input': [{'type': 'text', 'text': 'synthetic seed'}]}, 3)
            await until(lambda v: v.get('method') == 'turn/completed')
            compact_start = time.monotonic()
            await send('thread/compact/start', {'threadId': tid}, 4)
            done = await until(lambda v: v.get('method') == 'item/completed' and
                v.get('params', {}).get('item', {}).get('type') == 'contextCompaction')
            result.update(completed=True, compact_elapsed_seconds=round(time.monotonic()-compact_start, 3))
        except Exception as exc:
            result.update(completed=False, error=repr(exc))
        finally:
            proc.terminate()
            try:
                await asyncio.wait_for(proc.wait(), 4)
            except asyncio.TimeoutError:
                proc.kill()
                await proc.wait()
            await asyncio.gather(*readers, return_exceptions=True)
            await runner.cleanup()
        result['requests'] = requests
        result['notifications'] = [{'method': v['method'], 'message': v.get('params', {}).get('message') or v.get('params', {}).get('error', {}).get('message')}
                                   for v in notices if v.get('method') in ['error', 'warning']]
        result['server_errors'] = server_errors
        result['protocol_pings_sent'] = ping_count
        print(json.dumps(result), flush=True)
        return result
    
    
    async def main():
        results = []
        for args in [('fast', 0.2, False), ('slow_silent_ws', 1.6, False),
                     ('slow_heartbeat_ws', 1.6, True), ('slow_direct_http', 1.6, False, False),
                     ('slow_protocol_ping_ws', 1.6, False, True, True)]:
            results.append(await scenario(*args))
        (ROOT/'protocol-probe-results.json').write_text(json.dumps(results, indent=2))
        assert all(r['completed'] for r in results), 'At least one fixture failed; inspect artifacts'
        assert [x['transport'] for x in results[0]['requests'] if x['compact']] == ['ws']
        assert [x['transport'] for x in results[1]['requests'] if x['compact']] == ['ws', 'ws', 'ws', 'http']
        assert [x['transport'] for x in results[2]['requests'] if x['compact']] == ['ws']
        assert [x['transport'] for x in results[3]['requests'] if x['compact']] == ['http']
        assert [x['transport'] for x in results[4]['requests'] if x['compact']] == ['ws', 'ws', 'ws', 'http']
        assert results[4]['protocol_pings_sent'] > 0
        print('PASS: WS retry amplification reproduced without subagents; JSON heartbeat avoids it, protocol Ping alone does not.')
    
    
    if __name__ == '__main__':
        asyncio.run(main())
  4. smixs commented on Sep 13, 2026

    @smixs

    Подтверждаю два случая на macOS с проверкой границы, на которой останавливается работа. Добавляю к этому issue, поскольку совпадают зависание компактации, WebSocket timeout/reset и отсутствие надёжного продолжения после перезапуска.

    Первый случай: 5–6 сентября 2026 года

    • Desktop 26.901.41123, build 7942.
    • Затронутый встроенный движок: codex-cli 0.153.3. Отдельно установленный CLI имел другую версию и не обслуживал эту задачу.
    • macOS 26.6.2, arm64; ChatGPT-аутентификация; gpt-6-astra, reasoning high.
    • Эффективный порог компактации: 244800; полное окно: 258400 токенов.

    Сначала одна автоматическая компактация успешно завершилась: после неё журнал показывал около 49 878 токенов контекста. После повторного накопления контекста следующая компактация перестала завершаться.

    Все времена UTC:

    Время Наблюдение
    2026-09-05 23:45:50 Контекст достиг 244 881 токена и снова превысил порог 244 800.
    23:50:54 remote compaction v2 stream failed; retrying request after delay, retries=1 max_retries=2, idle timeout waiting for websocket.
    23:55:59 Та же ошибка, retries=2 max_retries=2.
    23:59:53 Прерывание пользователем; второй успешный compacted не появился.
    2026-09-06 00:00:17 → 00:05:23 Новая попытка снова закончилась тем же таймаутом.
    00:08:21 Сессия восстановлена после перезапуска Desktop.
    00:14:39 → 00:19:42 Ручная компактация повторила таймаут примерно через 303 секунды.
    00:21:36 Ручная попытка прервана после 416 957 мс.

    Типичная ошибка:

    remote compaction v2 stream failed; retrying request after delay
    retries=1 max_retries=2
    compact_error=stream disconnected before completion: idle timeout waiting for websocket
    

    WebSocket успешно устанавливался и переустанавливался. Между неудачными компактациями короткие обычные ответы в этой же задаче могли завершаться. Это сужает наблюдаемую проблему до пути компактации, но не доказывает конкретную серверную или клиентскую первопричину.

    Сохранённый исходный журнал: 1 040 корректных JSONL-записей, 17 597 112 байт, одна успешная компактация. Исследование не обнаружило в сохранённых предупреждениях/ошибках этой задачи 401, 429 или сообщения о повреждении SQLite.

    После сохранения исходного журнала был выполнен отдельный ограниченный тест того же движка с remote_compaction_v2=false. Это не оказалось рабочим восстановлением: thread/compact/start обратился к старому маршруту и немедленно получил:

    Error running remote compact task: unexpected status 404 Not Found
    {"detail":"Not Found"}
    https://chatgpt.com/backend-api/codex/responses/compact
    

    Изменение флага было ограничено диагностическим процессом; глобальная конфигурация не изменялась. Не предлагаю отключение v2 как обходной путь.

    Второй случай: 13 сентября 2026 года

    • Desktop 26.908.40834, build 8881; метаданные сессии 0.154.0-alpha.6.2, журналы рабочего процесса также указывают 0.154.0.
    • macOS 27.0 (26A428), arm64; gpt-6-astra, xhigh.

    В 03:16:16 UTC, сразу после завершённого вызова инструмента, начался run_auto_compact{reason=ContextLimit phase=MidTurn}: 247 167 токенов при пороге 244 800.

    В 03:18:34 UTC:

    remote compaction v2 stream failed; retrying request after delay
    retries=1 max_retries=2
    compact_error=stream disconnected before completion:
    WebSocket protocol error: Connection reset without closing handshake
    

    Последующие попытки снова попадали в run_pre_sampling_compact / phase=PreTurn перед ответом. Во время проверки интерфейсный статус оставался active / inProgress, с error=null, без нового сообщения или инструмента.

    В отличие от первого случая, 13 сентября восстановление в итоге произошло: событие compacted сохранено в 03:24:57.209 UTC, после последующих действий пользователя задача продолжила работу. Это примерно 8 минут 41 секунда от первого старта компактации до успешного события, включая прерывания и повторные попытки, а не длительность одного запроса. Не утверждаю, что этот случай был необратимым зависанием.

    Позже в этой же задаче возникла другая проблема: завершённые команды оставались визуально работающими, а часть истории исчезла после повторного открытия. Детали добавлены в #42311. Совпадение в одной задаче не доказывает общую причину с транспортным сбоем.

    Просьба к разработчикам

    Нужны ограниченное по времени восстановление компактации, точное отображение состояния ожидания/ошибки и поддерживаемый путь продолжения существующей задачи. Перезапуск и повторное «продолжай» не должны оставлять пользователя в цикле той же компактации без понятного результата. Также нужен рабочий поддерживаемый вариант восстановления, когда v2 повторно истекает по таймауту, а прежний endpoint возвращает 404.

    Это технические выдержки из двух локально исследованных случаев. Полная переписка, исходные журналы, идентификаторы задач, частные пути и учётные данные не публикуются. Модель, сеть или один конкретный компонент Codex не объявляются установленной первопричиной.

  5. HonkCY commented on Sep 14, 2026

    @HonkCY

    This is still happening on Codex Desktop 26.908.40834 on macOS.
    Automatic compaction gets stuck indefinitely at “Optimizing conversation”. I’m currently watching it sit there for minutes with no useful progress and no actionable error state. This is not an edge-case cosmetic bug. Compaction is on the critical path of long-running Codex work, so when it hangs, the entire thread becomes unusable.
    The reliability of Codex Desktop is frankly unacceptable for a paid development tool. I encounter workflow-breaking issues like this constantly. A coding agent cannot be considered reliable if basic session lifecycle operations such as compaction can silently hang and force users to abandon or reconstruct active work.
    Please stop presenting an indefinite spinner as “working”. At minimum:

    1. put a hard timeout on compaction;
    2. expose the actual failure/retry state;
    3. allow a failed compaction to be retried or bypassed safely;
    4. preserve the existing thread instead of leaving it wedged;
    5. add telemetry so users can distinguish model work, backend queueing, compaction, retries, and a dead operation.
      I should not have to treat every long Codex thread as disposable because the harness may randomly wedge itself.
  6. yuchisong commented on Oct 3, 2026

    @yuchisong

    I hit similar compaction/reconnection symptoms on October 3, 2026, using the Codex extension in VS Code on a MacBook Pro. This issue is about the desktop app, so I'm not sure whether the extension case has the same cause.

    The UI showed compaction, then "Reconnecting 2/2", followed by:

    stream disconnected before completion: idle timeout waiting for websocket

    The "Working" timer showed roughly 5 hours 48 minutes, but I can't tell how much of that was productive execution versus waiting. A subsequent prompt asking for status also received no response. I restarted VS Code; recovery after the restart hasn't been confirmed. Exact macOS, VS Code, and extension versions are not available for this report yet.

    The practical impact is that an unattended workflow appeared to remain active without delivering a response or a clear recovery state. Could you triage whether this belongs with this bug or should be filed as a separate VS Code extension issue?

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