TUI history insertion flushes stdout once per row, defeating write batching and synchronized output

Open 💬 2 comments Opened Aug 18, 2026 by astral303
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.148.0-dev.1 (at 0.148.0-alpha.22 level + commits thru a998c7a1ce88a521d319a4f336e0a4bf36095637); reproduces on main @ a998c7a1ce88a521d319a4f336e0a4bf36095637

What subscription do you have?

ChatGPT Pro (20x)

Which model were you using?

Not model-specific; observed with gpt-5.6-sol

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What terminal emulator and version are you using (if applicable)?

Windows Terminal Canary 1.26.2182.0 with PowerShell 7.6.4. No terminal multiplexer. The mechanism is cross-platform; the visible impact is worst on Windows Terminal.

Codex doctor report

{
  "schemaVersion": 1,
  "generatedAt": "1787081413s since unix epoch",
  "overallStatus": "warning",
  "codexVersion": "0.148.0-dev.1",
  "checks": {
    "app_server.status": {
      "id": "app_server.status",
      "category": "app-server",
      "status": "ok",
      "summary": "background server is not running",
      "details": {
        "control socket": "C:\\Users\\micro\\.codex\\app-server-control\\app-server-control.sock",
        "daemon state dir": "C:\\Users\\micro\\.codex\\app-server-daemon",
        "mode": "ephemeral",
        "pid file": "C:\\Users\\micro\\.codex\\app-server-daemon\\app-server.pid (missing)",
        "settings": "C:\\Users\\micro\\.codex\\app-server-daemon\\settings.json (missing)",
        "status": "not running",
        "update-loop pid file": "C:\\Users\\micro\\.codex\\app-server-daemon\\app-server-updater.pid (missing)"
      },
      "remediation": null,
      "durationMs": 0
    },
    "auth.credentials": {
      "id": "auth.credentials",
      "category": "auth",
      "status": "ok",
      "summary": "auth is configured",
      "details": {
        "auth file": "C:\\Users\\micro\\.codex\\auth.json",
        "auth storage mode": "File",
        "stored API key": "false",
        "stored ChatGPT tokens": "true",
        "stored agent identity": "false",
        "stored auth mode": "chatgpt"
      },
      "remediation": null,
      "durationMs": 0
    },
    "config.load": {
      "id": "config.load",
      "category": "config",
      "status": "ok",
      "summary": "config loaded",
      "details": {
        "CODEX_HOME": "C:\\Users\\micro\\.codex",
        "config.toml": "C:\\Users\\micro\\.codex\\config.toml",
        "config.toml parse": "ok",
        "cwd": "C:\\Users\\micro\\Documents\\3rd-party-sources\\codex\\codex-rs",
        "enabled feature flags": "<redacted>",
        "feature flag overrides": "none",
        "feature flags enabled": "41",
        "log dir": "C:\\Users\\micro\\.codex\\log",
        "mcp servers": "5",
        "model": "gpt-5.6-luna",
        "model provider": "openai",
        "sqlite home": "C:\\Users\\micro\\.codex"
      },
      "remediation": null,
      "durationMs": 1
    },
    "git.environment": {
      "id": "git.environment",
      "category": "git",
      "status": "ok",
      "summary": "git version 2.55.0.windows.2",
      "details": {
        ".git entry": "directory",
        "PATH git #1": "C:\\Program Files\\Git\\cmd\\git.exe",
        "PATH git entries": "1",
        "git branch": "fix/tui-single-write-history-insertion",
        "git build options": "git version 2.55.0.windows.2; cpu: x86_64; built from commit: c8dff95af823bd04181a583c03f3fbb954551c30; sizeof-long: 4; sizeof-size_t: 8; shell-path: D:/git-sdk-64-build-installers/usr/bin/sh; rust: disabled; feature: fsmonitor--daemon; gettext: enabled; libcurl: 8.21.0; OpenSSL: OpenSSL 3.5.7 9 Jun 2026; zlib: 1.3.2; SHA-1: SHA1_DC; SHA-256: SHA256_BLK; default-ref-format: files; default-hash: sha1",
        "git exec path": "C:/Program Files/Git/mingw64/libexec/git-core",
        "git version": "git version 2.55.0.windows.2",
        "repo detected": "true",
        "repo root": "C:\\Users\\micro\\Documents\\3rd-party-sources\\codex",
        "selected git": "C:\\Program Files\\Git\\cmd\\git.exe"
      },
      "remediation": null,
      "durationMs": 115
    },
    "git.worktree.dev_drive": {
      "id": "git.worktree.dev_drive",
      "category": "git",
      "status": "warning",
      "summary": "this worktree is not on a Windows Dev Drive; moving it to a trusted Dev Drive can significantly improve repository and filesystem performance",
      "details": {},
      "remediation": "create a trusted Windows Dev Drive for source repositories: https://learn.microsoft.com/en-us/<redacted>",
      "durationMs": 0
    },
    "installation": {
      "id": "installation",
      "category": "install",
      "status": "ok",
      "summary": "installation looks consistent",
      "details": {
        "PATH codex #1": "C:\\Users\\micro\\AppData\\Local\\Programs\\OpenAI\\Codex\\bin\\codex.exe",
        "current executable": "C:\\Users\\micro\\AppData\\Local\\Temp\\codex-all-fixes-validation\\codex-debug-all-fixes-15df0365b1-v0.148.0-dev.1-20260817-015828.exe",
        "install context": "other",
        "managed by bun": "false",
        "managed by npm": "false",
        "managed by pnpm": "false",
        "managed package root": "not set"
      },
      "remediation": null,
      "durationMs": 55
    },
    "mcp.config": {
      "id": "mcp.config",
      "category": "mcp",
      "status": "ok",
      "summary": "MCP configuration is locally consistent",
      "details": {
        "configured servers": "5",
        "disabled servers": "4",
        "stdio servers": "1",
        "streamable_http servers": "4"
      },
      "remediation": null,
      "durationMs": 59
    },
    "network.env": {
      "id": "network.env",
      "category": "network",
      "status": "ok",
      "summary": "network-related environment looks readable",
      "details": {
        "proxy env vars": "none"
      },
      "remediation": null,
      "durationMs": 0
    },
    "network.provider_reachability": {
      "id": "network.provider_reachability",
      "category": "reachability",
      "status": "ok",
      "summary": "active provider endpoints are reachable over HTTP",
      "details": {
        "ChatGPT base URL": "https://chatgpt.com/backend-api/ reachable (HTTP 403)",
        "reachability mode": "ChatGPT auth"
      },
      "remediation": null,
      "durationMs": 131
    },
    "network.websocket_reachability": {
      "id": "network.websocket_reachability",
      "category": "websocket",
      "status": "ok",
      "summary": "Responses WebSocket handshake succeeded",
      "details": {
        "DNS": "2 IPv4, 0 IPv6, first IPv4",
        "auth mode": "chatgpt",
        "connect timeout": "15000 ms",
        "endpoint": "wss://chatgpt.com/backend-api/<redacted>",
        "handshake result": "HTTP 101 Switching Protocols",
        "model provider": "openai",
        "provider name": "OpenAI",
        "proxy env vars": "none",
        "reasoning header": "false",
        "server model present": "false",
        "supports websockets": "true",
        "wire API": "responses"
      },
      "remediation": null,
      "durationMs": 719
    },
    "runtime.provenance": {
      "id": "runtime.provenance",
      "category": "runtime",
      "status": "ok",
      "summary": "running local build on windows-x86_64",
      "details": {
        "commit": "unknown",
        "current executable": "C:\\Users\\micro\\AppData\\Local\\Temp\\codex-all-fixes-validation\\codex-debug-all-fixes-15df0365b1-v0.148.0-dev.1-20260817-015828.exe",
        "install method": "other",
        "platform": "windows-x86_64",
        "version": "0.148.0-dev.1"
      },
      "remediation": null,
      "durationMs": 0
    },
    "runtime.search": {
      "id": "runtime.search",
      "category": "search",
      "status": "ok",
      "summary": "search is OK (system)",
      "details": {
        "search command": "rg.exe",
        "search command readiness": "ripgrep 15.1.0 (rev af60c2de9d)",
        "search provider": "system"
      },
      "remediation": null,
      "durationMs": 8
    },
    "sandbox.helpers": {
      "id": "sandbox.helpers",
      "category": "sandbox",
      "status": "ok",
      "summary": "sandbox configuration is readable",
      "details": {
        "approval policy": "OnRequest",
        "codex-linux-sandbox helper": "none",
        "execve wrapper helper": "none",
        "filesystem sandbox": "restricted",
        "network sandbox": "restricted"
      },
      "remediation": null,
      "durationMs": 0
    },
    "security.endpoint": {
      "id": "security.endpoint",
      "category": "security",
      "status": "warning",
      "summary": "endpoint protection detected; Codex exclusions are unverified",
      "details": {
        "Codex exclusions": "not verified",
        "endpoint products": "Microsoft Defender",
        "exclusion targets": "signed Codex app; codex.exe; codex-windows-sandbox-setup.exe; codex-command-runner.exe; codex-code-mode-host.exe"
      },
      "issues": [
        {
          "severity": "warning",
          "cause": "Microsoft Defender can interfere with Codex. Verify Codex exclusions.",
          "measured": "not verified",
          "expected": "Codex application and helper exclusions",
          "remedy": "Microsoft Defender: Add a certificate or executable-path exclusion for Codex and its helpers. If Attack Surface Reduction blocks Codex, add a rule exclusion. If Controlled Folder Access blocks Codex, allow the app.",
          "fields": [
            "Codex exclusions"
          ]
        }
      ],
      "remediation": "ask your security administrator to verify Codex exclusions and required helper allowances",
      "durationMs": 23
    },
    "state.paths": {
      "id": "state.paths",
      "category": "state",
      "status": "ok",
      "summary": "state paths and databases are inspectable",
      "details": {
        "CODEX_HOME": "C:\\Users\\micro\\.codex (dir)",
        "active rollout files": "998 files, 4270930981 total bytes, 4279489 average bytes",
        "archived rollout files": "0 files, 0 total bytes, 0 average bytes",
        "goals DB": "C:\\Users\\micro\\.codex\\goals_1.sqlite (file)",
        "goals DB integrity": "ok",
        "log DB": "C:\\Users\\micro\\.codex\\logs_2.sqlite (file)",
        "log DB integrity": "ok",
        "log dir": "C:\\Users\\micro\\.codex\\log (missing)",
        "memories DB": "C:\\Users\\micro\\.codex\\memories_1.sqlite (file)",
        "memories DB integrity": "ok",
        "queue DB": "C:\\Users\\micro\\.codex\\queue_1.sqlite (file)",
        "queue DB integrity": "ok",
        "sqlite home": "C:\\Users\\micro\\.codex (dir)",
        "state DB": "C:\\Users\\micro\\.codex\\state_5.sqlite (file)",
        "state DB integrity": "ok",
        "thread history DB": "C:\\Users\\micro\\.codex\\thread_history_1.sqlite (file)",
        "thread history DB integrity": "ok"
      },
      "remediation": null,
      "durationMs": 4751
    },
    "state.rollout_db_parity": {
      "id": "state.rollout_db_parity",
      "category": "threads",
      "status": "warning",
      "summary": "rollout files and state DB thread inventory differ",
      "details": {
        "default model provider": "openai",
        "rollout DB active files": "998",
        "rollout DB active rows": "999",
        "rollout DB archive mismatches": "0",
        "rollout DB archived files": "0",
        "rollout DB archived rows": "0",
        "rollout DB duplicate DB paths": "0",
        "rollout DB duplicate rollout thread ids": "0",
        "rollout DB malformed file names": "0",
        "rollout DB missing active rows": "0",
        "rollout DB missing archived rows": "0",
        "rollout DB model providers": "openai=999",
        "rollout DB rows": "999",
        "rollout DB scan cap reached": "false",
        "rollout DB scan errors": "0",
        "rollout DB sources": "subagent:thread_spawn=831, subagent:other=96, cli=71, unknown=1",
        "rollout DB stale row sample": "C:\\Users\\micro\\.codex\\sessions\\2026\\08\\12\\rollout-2026-08-12T14-38-21-019ff744-aed1-7231-a9a0-ac603fea4078.jsonl",
        "rollout DB stale rows": "1"
      },
      "issues": [
        {
          "severity": "warning",
          "cause": "state DB rows point at missing or unusable rollout files",
          "measured": "1 stale rows",
          "expected": "every state DB rollout path is a file on disk",
          "remedy": null,
          "fields": []
        }
      ],
      "remediation": null,
      "durationMs": 1058
    },
    "system.disk": {
      "id": "system.disk",
      "category": "disk",
      "status": "ok",
      "summary": "sufficient free disk space (30.2 GiB)",
      "details": {
        "CODEX_HOME available": "30.2 GiB",
        "failure threshold": "1.0 GiB",
        "warning threshold": "5.0 GiB",
        "worktree available": "30.2 GiB"
      },
      "remediation": null,
      "durationMs": 0
    },
    "system.environment": {
      "id": "system.environment",
      "category": "system",
      "status": "ok",
      "summary": "OS language en-US",
      "details": {
        "EDITOR": "set",
        "PAGER": "set",
        "VISUAL": "not set",
        "os": "Windows 10.0.26200 (Windows 11 Professional) [64-bit]",
        "os language": "en-US",
        "os type": "Windows",
        "os version": "10.0.26200"
      },
      "remediation": null,
      "durationMs": 0
    },
    "terminal.env": {
      "id": "terminal.env",
      "category": "terminal",
      "status": "ok",
      "summary": "terminal metadata was detected",
      "details": {
        "COLORTERM": "truecolor",
        "WT_SESSION": "present",
        "color output": "disabled (stdout is not a terminal)",
        "console input code page": "437",
        "console output code page": "65001",
        "stderr console mode": "0x00000007 (VT processing: true)",
        "stderr is terminal": "true",
        "stdin is terminal": "true",
        "stdout console mode": "unavailable",
        "stdout is terminal": "false",
        "terminal": "Windows Terminal",
        "terminal size": "156x36"
      },
      "remediation": null,
      "durationMs": 0
    },
    "terminal.title": {
      "id": "terminal.title",
      "category": "title",
      "status": "ok",
      "summary": "terminal title default",
      "details": {
        "terminal title activity": "true",
        "terminal title items": "activity, project-name",
        "terminal title project source": "git repo root",
        "terminal title project value": "codex",
        "terminal title source": "default"
      },
      "remediation": null,
      "durationMs": 0
    },
    "updates.status": {
      "id": "updates.status",
      "category": "updates",
      "status": "ok",
      "summary": "update configuration is locally consistent",
      "details": {
        "cached latest version": "0.147.0",
        "check for update on startup": "true",
        "last checked at": "2026-08-17T16:39:27.066152100Z",
        "latest version": "0.147.0",
        "latest version status": "current version is not older",
        "update action": "manual or unknown",
        "version cache": "C:\\Users\\micro\\.codex\\version.json"
      },
      "remediation": null,
      "durationMs": 171
    }
  }
}

What issue are you seeing?

Every multi-row history insertion in the TUI reaches the terminal as one write syscall per row instead of one batched write.

insert_history_hyperlink_lines_with_mode_and_wrap_policy writes each row with queue!(writer, Print("\r\n")) directly against the terminal backend (insert_history.rs#L139-L154, #L203-L216). The backend is CrosstermBackend<Stdout> over std::io::stdout() (tui.rs#L434), and Rust's Stdout is line-buffered when connected to a terminal, so each \r\n forces an immediate flush. queue! batching is silently defeated by the writer it queues into.

Effects:

  1. Visible row-by-row rendering of large insertions. Each per-row write is an independently renderable chunk, so the terminal can paint between rows. This is the visible "dribble" in the transcript replay recordings on #37759 and #38839, but the mechanism applies to any large insertion — session resume, big tool output, scrollback repair.
  2. Synchronized output (DECSET 2026) cannot hold the frame. Windows Terminal force-releases a synchronized update 100 ms after DECSET 2026 h. Thousands of per-row syscalls consume that budget, so large insertions escape the envelope partially painted. Any atomicity fix for #38839 that defers the clear into the sync update still repaints mid-replay unless the insertion is batched — single-write insertion is a precondition, not an optimization.
  3. Thousands of independently paintable chunks per large insertion. Resize reflow may emit up to WINDOWS_TERMINAL_RESIZE_REFLOW_MAX_ROWS = 9_001 rows (resize_reflow_cap.rs#L20), i.e. ~9,000 write syscalls per reflow. The syscall overhead itself is minor — the CPU stall in #38839 is dominated by app-side reflow computation — but every flush is a point where the terminal can paint a partially applied insertion. It probably also exacerbates #38479 (conhost over RDP), where per-write processing costs more.

What steps can reproduce the bug?

  1. Resume a session with a long transcript (or trigger resize reflow per #38839's steps) in inline mode.
  2. Observe the insertion painting row-by-row rather than appearing as one frame. A syscall trace (ProcMon on Windows, strace -e write elsewhere) shows one write per history row during the insertion.

What is the expected behavior?

The complete insertion is prepared in memory and handed to the backend as a single write, making one insertion one renderable unit and letting synchronized updates fit their time budget.

Additional information

Issue introduced 0.11.0: the per-row write dates to PR #1710, which introduced the escape-sequence insertion path as queue!(std::io::stdout(), Print("\r\n")) in a per-line loop - every release since inserts history one flushed row at a time.

Candidate fix with a regression test: https://github.com/astral303/codex/compare/main...astral303:codex:fix/tui-single-write-history-insertion :

  • the insertion path builds the complete ANSI batch in a Vec<u8> before touching the backend, removing per-row flushes while keeping terminal-state logic and the failure boundary unchanged
  • a test asserts a multi-row insertion reaches the backend as exactly one write
  • PR #1710 tests still pass

Interactive Windows Terminal regression coverage of the surrounding replay behavior (real 120x30 session, 1,500 long rows, resize cycles, visible-frame sampling) is in astral303/codex#7.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 9 days ago

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

  • #38839
  • #38479

Powered by Codex Action

astral303 · 9 days ago

This issue is not the same as #38839:

  • #38839 is a sequencing defect: after a deferred resize reflow, the clear is flushed immediately and the rebuild only happens at the next draw. It exists even with perfect write batching.
  • This issue is a write-granularity defect: any multi-row insertion (session resume, large tool output, scrollback repair) reaches the terminal one flushed row at a time. It exists even when no clear happens.
  • They interact — per-row flushing is one reason #38839's replay paints row by row — but fixing either does not fix the other.

And issue #38479 is referencing unexpected reflow events and performance over RDP, which is likely made worse by this issue — each flush is a separately processed chunk for conhost and a potential RDP update — but per-row flushing can't explain all of its symptoms; why those reflow replays trigger at all is a separate mechanism.