Codex TUI should detect truecolor over SSH when COLORTERM is not forwarded

Open 💬 3 comments Opened May 20, 2026 by lewis6991

What version of Codex CLI is running?

codex-cli 0.132.0

What subscription do you have?

Enterprise

Which model were you using?

gpt-5.5

What platform is your computer?

Darwin 24.6.0 arm64 arm

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

ghostty 1.3.1

Codex doctor report

{
  "schemaVersion": 1,
  "generatedAt": "1779272921s since unix epoch",
  "overallStatus": "warning",
  "codexVersion": "0.132.0",
  "checks": {
    "app_server.status": {
      "id": "app_server.status",
      "category": "app-server",
      "status": "ok",
      "summary": "background server is not running",
      "details": {
        "control socket": "/Users/lewrus01/.codex/app-server-control/app-server-control.sock",
        "daemon state dir": "/Users/lewrus01/.codex/app-server-daemon",
        "mode": "ephemeral",
        "pid file": "/Users/lewrus01/.codex/app-server-daemon/app-server.pid (missing)",
        "settings": "/Users/lewrus01/.codex/app-server-daemon/settings.json (missing)",
        "status": "not running",
        "update-loop pid file": "/Users/lewrus01/.codex/app-server-daemon/app-server-updater.pid (missing)"
      },
      "remediation": null,
      "durationMs": 0
    },
    "auth.credentials": {
      "id": "auth.credentials",
      "category": "auth",
      "status": "ok",
      "summary": "OpenAI auth is not required for the active model provider",
      "details": {
        "auth env vars present": "OPENAI_API_KEY",
        "auth file": "/Users/lewrus01/.codex/auth.json",
        "auth storage mode": "File",
        "model provider requires OpenAI auth": "false"
      },
      "remediation": null,
      "durationMs": 0
    },
    "config.load": {
      "id": "config.load",
      "category": "config",
      "status": "ok",
      "summary": "config loaded",
      "details": {
        "CODEX_HOME": "/Users/lewrus01/.codex",
        "config.toml": "/Users/lewrus01/.codex/config.toml",
        "config.toml parse": "ok",
        "cwd": "/Users/lewrus01/projects/neovim",
        "enabled feature flags": "shell_tool, unified_exec, shell_snapshot, terminal_resize_reflow, sqlite, memories, hooks, enable_request_compression, multi_agent, apps, tool_search, tool_suggest, plugins, plugin_hooks, in_app_browser, browser_use, browser_use_external, computer_use, plugin_sharing, external_migration, image_generation, skill_mcp_dependency_install, mentions_v2, steer, guardian_approval, goals, collaboration_modes, tool_call_mcp_elicitation, personality, fast_mode, tui_app_server, prevent_idle_sleep, workspace_dependencies",
        "feature flag overrides": "memories=true, external_migration=true, mentions_v2=true, goals=true, prevent_idle_sleep=true",
        "feature flags enabled": "33",
        "log dir": "/Users/lewrus01/.codex/log",
        "mcp servers": "0",
        "model": "gpt-5.5",
        "model provider": "proxy",
        "sqlite home": "/Users/lewrus01/.codex"
      },
      "remediation": null,
      "durationMs": 0
    },
    "installation": {
      "id": "installation",
      "category": "install",
      "status": "ok",
      "summary": "installation looks consistent",
      "details": {
        "PATH codex #1": "/Users/lewrus01/.local/bin/aarch64/codex",
        "PATH codex #2": "/opt/homebrew/bin/codex",
        "PATH codex #3": "/Users/lewrus01/.local/bin/aarch64/codex",
        "PATH codex entries": "3",
        "current executable": "/Users/lewrus01/.local/bin/aarch64/codex",
        "install context": "other",
        "managed by bun": "false",
        "managed by npm": "false",
        "managed package root": "not set"
      },
      "remediation": null,
      "durationMs": 11
    },
    "mcp.config": {
      "id": "mcp.config",
      "category": "mcp",
      "status": "ok",
      "summary": "no MCP servers configured",
      "details": {},
      "remediation": null,
      "durationMs": 0
    },
    "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": {
        "proxy API base URL": "https://openai-api-proxy.geo.arm.com/api/<redacted> reachable (HTTP 404)",
        "proxy API route probe": "https://openai-api-proxy.geo.arm.com/api/<redacted> route exists (HTTP 401)",
        "reachability mode": "provider auth"
      },
      "remediation": null,
      "durationMs": 455
    },
    "network.websocket_reachability": {
      "id": "network.websocket_reachability",
      "category": "websocket",
      "status": "ok",
      "summary": "Responses WebSocket is not enabled for the active provider",
      "details": {
        "model provider": "proxy",
        "provider name": "OpenAI",
        "proxy env vars": "none",
        "supports websockets": "false",
        "wire API": "responses"
      },
      "remediation": null,
      "durationMs": 0
    },
    "runtime.provenance": {
      "id": "runtime.provenance",
      "category": "runtime",
      "status": "ok",
      "summary": "running local build on macos-aarch64",
      "details": {
        "commit": "unknown",
        "current executable": "/Users/lewrus01/.local/bin/aarch64/codex",
        "install method": "other",
        "platform": "macos-aarch64",
        "version": "0.132.0"
      },
      "remediation": null,
      "durationMs": 0
    },
    "runtime.search": {
      "id": "runtime.search",
      "category": "search",
      "status": "ok",
      "summary": "search is OK (system)",
      "details": {
        "search command": "rg",
        "search command readiness": "ripgrep 15.1.0",
        "search provider": "system"
      },
      "remediation": null,
      "durationMs": 12
    },
    "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": "/Users/lewrus01/.codex/tmp/arg0/codex-arg0nKTeYf/codex-execve-wrapper",
        "filesystem sandbox": "restricted",
        "network sandbox": "enabled"
      },
      "remediation": null,
      "durationMs": 0
    },
    "state.paths": {
      "id": "state.paths",
      "category": "state",
      "status": "ok",
      "summary": "state paths and databases are inspectable",
      "details": {
        "CODEX_HOME": "/Users/lewrus01/.codex (dir)",
        "active rollout files": "3142 files, 6862807356 total bytes, 2184216 average bytes",
        "archived rollout files": "103 files, 443752655 total bytes, 4308278 average bytes",
        "log DB": "/Users/lewrus01/.codex/logs_2.sqlite (file)",
        "log DB integrity": "ok",
        "log dir": "/Users/lewrus01/.codex/log (dir)",
        "sqlite home": "/Users/lewrus01/.codex (dir)",
        "state DB": "/Users/lewrus01/.codex/state_5.sqlite (file)",
        "state DB integrity": "ok"
      },
      "remediation": null,
      "durationMs": 367
    },
    "terminal.env": {
      "id": "terminal.env",
      "category": "terminal",
      "status": "ok",
      "summary": "terminal metadata was detected",
      "details": {
        "COLORTERM": "truecolor",
        "TERM": "xterm-ghostty",
        "TERMINFO": "/Applications/Ghostty.app/Contents/Resources/terminfo (dir)",
        "TERM_PROGRAM": "ghostty",
        "color output": "enabled",
        "effective locale": "en_GB.UTF-8",
        "multiplexer": "tmux 3.6a",
        "stderr is terminal": "true",
        "stdin is terminal": "true",
        "stdout is terminal": "true",
        "terminal": "Ghostty",
        "terminal size": "186x41",
        "terminal version": "1.3.1",
        "tmux allow-passthrough": "off",
        "tmux client termname": "xterm-ghostty",
        "tmux client termtype": "ghostty 1.3.1",
        "tmux extended-keys": "off",
        "tmux focus-events": "on",
        "tmux set-clipboard": "on",
        "tmux xterm-keys": "on"
      },
      "remediation": null,
      "durationMs": 74
    },
    "updates.status": {
      "id": "updates.status",
      "category": "updates",
      "status": "warning",
      "summary": "update configuration is locally consistent",
      "details": {
        "cached latest version": "0.131.0",
        "check for update on startup": "true",
        "last checked at": "2026-05-19T14:44:24.550923Z",
        "latest version probe": "curl: (56) The requested URL returned error: 403",
        "update action": "manual or unknown",
        "version cache": "/Users/lewrus01/.codex/version.json"
      },
      "remediation": null,
      "durationMs": 1186
    }
  }
}

What issue are you seeing?

When running Codex on a remote host over SSH, the Codex TUI does not appear to detect truecolor support unless COLORTERM=truecolor is present in the remote environment.

In this setup, Ghostty is the local terminal, and SSH correctly propagates TERM=xterm-ghostty to the remote host. The remote host also has a valid xterm-ghostty terminfo entry that advertises truecolor support. However, COLORTERM is not forwarded by SSH, which is common unless both client and server SSH config are set up for SendEnv/AcceptEnv.

As a result, Codex falls back to degraded color rendering on the remote host, even though the terminal capability information available remotely is sufficient to determine that truecolor is supported.

What steps can reproduce the bug?

  1. Use Ghostty locally.
  1. SSH into a remote Linux host where COLORTERM is not forwarded:

``bash
ssh <remote-host>
``

  1. Confirm the remote shell sees Ghostty's TERM, but not COLORTERM:

``bash
echo "$TERM"
echo "${COLORTERM:-unset}"
``

In my case:

``text
xterm-ghostty
unset
``

  1. If the remote host does not have Ghostty terminfo installed, install it from the local machine:

``bash
infocmp -x xterm-ghostty | ssh <remote-host> 'tic -x -'
``

  1. On the remote host, verify terminfo is available and advertises truecolor:

``bash
infocmp -x "$TERM" >/dev/null && echo terminfo-ok
tput colors
infocmp -x "$TERM" | grep -E '(^|,)(Tc|RGB)(,|$)|setrgb[fb]='
``

In my case, infocmp -x xterm-ghostty includes Tc, setrgbf, and setrgbb.

  1. Run Codex on the remote host:

``bash
codex
``

  1. Compare with forcing COLORTERM manually:

``bash
COLORTERM=truecolor codex
``

Codex renders correctly with COLORTERM=truecolor, but not when relying on the installed xterm-ghostty terminfo alone.

What is the expected behavior?

Codex should detect that the terminal supports truecolor when running over SSH without requiring COLORTERM to be forwarded.

If environment-based detection does not report 24-bit color and COLORTERM is missing, Codex should query the terminal directly with terminal control-sequence requests instead of relying only on inherited environment variables.

Relevant terminal-query mechanisms:

Alternatively, Codex could use infocmp -x "$TERM" as a conservative fallback when terminal queries are unavailable, and treat Tc, RGB, or both setrgbf and setrgbb as evidence of truecolor support.

Additional information

_No response_

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗