Lumoswitch Docs

ChatGPT Desktop (Codex) setup

Codex in ChatGPT desktop is a graphical coding Agent for local projects. Choose this guide if you use the macOS or Windows ChatGPT desktop app and do not plan to run Codex in a terminal. You do not need to install Codex CLI; Terminal or PowerShell is needed only once to write the settings.

This setup affects only the Codex workspace inside ChatGPT desktop. Ordinary ChatGPT conversations, voice, and other ChatGPT features continue to use their original service.

Connection modes

ModeAvailablePurpose and reason
One-time launchNoDesktop launchers have no supported per-run config flags and do not reliably inherit shell or PowerShell variables
Persistent useYesSafely merges user-level Codex settings read by desktop, CLI, and IDE
Remove setupYesRemoves only the Lumoswitch provider, default selection, and dedicated Key while preserving other Codex settings

1. Prepare an API configuration

The API configuration must provide:

  • Responses output;
  • a valid Lumoswitch Access Key; and
  • the client-facing model name shown on the result page.

Replace each placeholder in the command with the real value from that result:

PlaceholderValue
{{api_base_url}}Lumoswitch API URL, normally ending in /v1
{{access_key}}The API configuration's Access Key; never share it or commit it to Git
{{model}}The client-facing model name, not an upstream platform display name
{{codex_model_catalog_base64}}Capability-aware Codex catalog, Base64 encoded

If the Access Key has appeared in a public chat, document, screenshot, or repository, rotate it before continuing.

2. Quit ChatGPT completely

On macOS, select ChatGPT > Quit ChatGPT or press Command + Q. On Windows, quit ChatGPT from its app menu and confirm that no ChatGPT process remains. Closing the window alone may not reload the provider and credential.

3. Persistent use

This setup has no temporary command. A desktop app opened from Finder, Dock, the Start menu, or a login item normally does not inherit the current terminal's temporary environment variables, so a temporary setup would not work reliably.

macOS

Run the complete command below in a trusted terminal:

set -e
LUMOSWITCH_CODEX_DIR="$HOME/.codex"
LUMOSWITCH_CODEX_CONFIG="$LUMOSWITCH_CODEX_DIR/config.toml"
LUMOSWITCH_CODEX_ENV="$LUMOSWITCH_CODEX_DIR/.env"
LUMOSWITCH_CODEX_CATALOG="$LUMOSWITCH_CODEX_DIR/lumoswitch-model-catalog.json"
LUMOSWITCH_CODEX_BIN="/Applications/ChatGPT.app/Contents/Resources/codex"
LUMOSWITCH_BACKUP_SUFFIX="$(date +%Y%m%d-%H%M%S)-$$"
LUMOSWITCH_CONFIG_TMP=""
LUMOSWITCH_ENV_TMP=""
LUMOSWITCH_CATALOG_TMP=""

lumoswitch_cleanup() {
  [ -z "$LUMOSWITCH_CONFIG_TMP" ] || rm -f "$LUMOSWITCH_CONFIG_TMP"
  [ -z "$LUMOSWITCH_ENV_TMP" ] || rm -f "$LUMOSWITCH_ENV_TMP"
  [ -z "$LUMOSWITCH_CATALOG_TMP" ] || rm -f "$LUMOSWITCH_CATALOG_TMP"
}
trap lumoswitch_cleanup EXIT HUP INT TERM

if pgrep -x ChatGPT >/dev/null 2>&1; then
  echo "Quit ChatGPT completely with Command + Q, then run this command again." >&2
  exit 1
fi

mkdir -p "$LUMOSWITCH_CODEX_DIR"
umask 077

if [ ! -x "$LUMOSWITCH_CODEX_BIN" ]; then
  echo "ChatGPT's bundled Codex executable was not found. Install or update ChatGPT for macOS first." >&2
  exit 1
fi

if [ -L "$LUMOSWITCH_CODEX_CONFIG" ] || [ -L "$LUMOSWITCH_CODEX_ENV" ] || [ -L "$LUMOSWITCH_CODEX_CATALOG" ]; then
  echo "A Codex settings, credential, or Lumoswitch model-catalog file is a symbolic link. Automatic merge stopped; edit its target manually." >&2
  exit 1
fi

if [ -e "$LUMOSWITCH_CODEX_CATALOG" ] && [ ! -f "$LUMOSWITCH_CODEX_CATALOG" ]; then
  echo "The Lumoswitch model-catalog path is occupied by a non-regular file. Automatic merge stopped: $LUMOSWITCH_CODEX_CATALOG" >&2
  exit 1
fi

LUMOSWITCH_MODEL_VALUE="$(/bin/cat <<'LUMOSWITCH_MODEL_VALUE_EOF'
{{model}}
LUMOSWITCH_MODEL_VALUE_EOF
)"
LUMOSWITCH_CODEX_CATALOG_BASE64="$(/bin/cat <<'LUMOSWITCH_CODEX_CATALOG_BASE64_EOF'
{{codex_model_catalog_base64}}
LUMOSWITCH_CODEX_CATALOG_BASE64_EOF
)"
LUMOSWITCH_MODEL_LITERAL="$(/usr/bin/osascript -l JavaScript - "$LUMOSWITCH_MODEL_VALUE" <<'LUMOSWITCH_STRING_JXA'
function run(argv) {
  return JSON.stringify(argv[0]);
}
LUMOSWITCH_STRING_JXA
)"
LUMOSWITCH_CATALOG_LITERAL="$(/usr/bin/osascript -l JavaScript - "$LUMOSWITCH_CODEX_CATALOG" <<'LUMOSWITCH_PATH_JXA'
function run(argv) {
  return JSON.stringify(argv[0]);
}
LUMOSWITCH_PATH_JXA
)"

if [ -f "$LUMOSWITCH_CODEX_CONFIG" ]; then
  cp "$LUMOSWITCH_CODEX_CONFIG" "$LUMOSWITCH_CODEX_CONFIG.backup.$LUMOSWITCH_BACKUP_SUFFIX"
  chmod 600 "$LUMOSWITCH_CODEX_CONFIG.backup.$LUMOSWITCH_BACKUP_SUFFIX"

  if awk '
    BEGIN { root = 1; found = 0 }
    /^[[:space:]]*\[/ { root = 0 }
    root && /^[[:space:]]*model_providers[[:space:]]*=/ { found = 1 }
    END { exit found ? 0 : 1 }
  ' "$LUMOSWITCH_CODEX_CONFIG"; then
    echo "Inline model_providers detected. Automatic merge stopped; merge it manually. The original file is unchanged." >&2
    exit 1
  fi

  if grep -Eq "^[[:space:]]*('features'([.][^[:space:]=]+)?[[:space:]]*=|\\[[^]]*'features')" "$LUMOSWITCH_CODEX_CONFIG"; then
    echo "Single-quoted features settings cannot be merged safely. Automatic merge stopped; the original file is unchanged." >&2
    exit 1
  fi

  if awk '
    BEGIN { root = 1; found = 0 }
    /^[[:space:]]*\[/ { root = 0 }
    root && /^[[:space:]]*(features|"features")([.][^[:space:]=]+)?[[:space:]]*=/ { found = 1 }
    END { exit found ? 0 : 1 }
  ' "$LUMOSWITCH_CODEX_CONFIG"; then
    echo "Top-level inline features settings cannot be merged safely into [features]. Automatic merge stopped; the original file is unchanged." >&2
    exit 1
  fi

  if awk '
    BEGIN { count = 0; invalid = 0 }
    /^[[:space:]]*\[/ {
      value = $0
      gsub(/[[:space:]]/, "", value)
      if (value == "[features]") count++
      else if (value ~ /^\[features[.]/ || value ~ /^\["features"/) invalid = 1
    }
    END { exit count > 1 || invalid ? 0 : 1 }
  ' "$LUMOSWITCH_CODEX_CONFIG"; then
    echo "Duplicate, quoted, or nested features tables cannot be merged safely. Automatic merge stopped; the original file is unchanged." >&2
    exit 1
  fi

  if grep -Eq '^[[:space:]]*\[[[:space:]]*model_providers[.]"lumoswitch"' "$LUMOSWITCH_CODEX_CONFIG"; then
    echo "A quoted Lumoswitch provider table was detected. Automatic merge stopped; merge it manually. The original file is unchanged." >&2
    exit 1
  fi

  if awk -v expected="$LUMOSWITCH_CATALOG_LITERAL" '
    BEGIN { root = 1; mismatch = 0 }
    /^[[:space:]]*\[/ { root = 0 }
    root && /^[[:space:]]*model_catalog_json[[:space:]]*=/ {
      value = $0
      sub(/^[[:space:]]*model_catalog_json[[:space:]]*=[[:space:]]*/, "", value)
      sub(/[[:space:]]*(#.*)?$/, "", value)
      if (value != expected) mismatch = 1
    }
    END { exit mismatch ? 0 : 1 }
  ' "$LUMOSWITCH_CODEX_CONFIG"; then
    echo "Another Codex custom model catalog is already configured. Automatic replacement stopped; merge the model manually instead." >&2
    exit 1
  fi
fi

LUMOSWITCH_CATALOG_TMP="$(mktemp "$LUMOSWITCH_CODEX_DIR/lumoswitch-model-catalog.json.lumoswitch.XXXXXX")"
printf '%s' "$LUMOSWITCH_CODEX_CATALOG_BASE64" | /usr/bin/base64 -D > "$LUMOSWITCH_CATALOG_TMP"
chmod 600 "$LUMOSWITCH_CATALOG_TMP"
LUMOSWITCH_CATALOG_TMP_LITERAL="$(/usr/bin/osascript -l JavaScript - "$LUMOSWITCH_CATALOG_TMP" <<'LUMOSWITCH_TMP_PATH_JXA'
function run(argv) {
  return JSON.stringify(argv[0]);
}
LUMOSWITCH_TMP_PATH_JXA
)"
"$LUMOSWITCH_CODEX_BIN" debug models -c "model_catalog_json=$LUMOSWITCH_CATALOG_TMP_LITERAL" >/dev/null

LUMOSWITCH_CONFIG_TMP="$(mktemp "$LUMOSWITCH_CODEX_DIR/config.toml.lumoswitch.XXXXXX")"
{
  printf 'model = %s\n' "$LUMOSWITCH_MODEL_LITERAL"
  printf 'model_provider = "lumoswitch"\n'
  printf 'model_catalog_json = %s\n' "$LUMOSWITCH_CATALOG_LITERAL"
  printf 'web_search = "disabled"\n'

  if [ -f "$LUMOSWITCH_CODEX_CONFIG" ]; then
    printf '\n'
    awk '
      BEGIN { root = 1; skip = 0; in_features = 0; features_seen = 0 }
      /^[[:space:]]*\[/ {
        if (in_features) {
          print "multi_agent = false"
          in_features = 0
        }
        root = 0
        if ($0 ~ /^[[:space:]]*\[[[:space:]]*model_providers[.]lumoswitch([.][^]]+)?[[:space:]]*\]/) {
          skip = 1
          next
        }
        skip = 0
        if ($0 ~ /^[[:space:]]*\[[[:space:]]*features[[:space:]]*\]/) {
          in_features = 1
          features_seen = 1
        }
      }
      skip { next }
      in_features && /^[[:space:]]*multi_agent[[:space:]]*=/ { next }
      root && /^[[:space:]]*(model|model_provider|model_catalog_json|model_reasoning_effort|web_search)[[:space:]]*=/ { next }
      { print }
      END {
        if (in_features) print "multi_agent = false"
        else if (!features_seen) {
          print ""
          print "[features]"
          print "multi_agent = false"
        }
      }
    ' "$LUMOSWITCH_CODEX_CONFIG"
  else
    printf '\n[features]\n'
    printf 'multi_agent = false\n'
  fi

  printf '\n[model_providers.lumoswitch]\n'
  printf 'name = "Lumoswitch"\n'
  printf 'base_url = "%s"\n' "{{api_base_url}}"
  printf 'env_key = "LUMOSWITCH_API_KEY"\n'
  printf 'wire_api = "responses"\n'
  printf 'http_headers = { "X-Lumoswitch-Agent" = "codex-v1" }\n'
  printf 'requires_openai_auth = false\n'
} > "$LUMOSWITCH_CONFIG_TMP"
chmod 600 "$LUMOSWITCH_CONFIG_TMP"
mv "$LUMOSWITCH_CATALOG_TMP" "$LUMOSWITCH_CODEX_CATALOG"
LUMOSWITCH_CATALOG_TMP=""
mv "$LUMOSWITCH_CONFIG_TMP" "$LUMOSWITCH_CODEX_CONFIG"
LUMOSWITCH_CONFIG_TMP=""

if [ -f "$LUMOSWITCH_CODEX_ENV" ]; then
  LUMOSWITCH_ENV_TMP="$(mktemp "$LUMOSWITCH_CODEX_DIR/.env.backup.lumoswitch.XXXXXX")"
  awk '!/^[[:space:]]*(export[[:space:]]+)?LUMOSWITCH_API_KEY[[:space:]]*=/' "$LUMOSWITCH_CODEX_ENV" > "$LUMOSWITCH_ENV_TMP"
  chmod 600 "$LUMOSWITCH_ENV_TMP"
  mv "$LUMOSWITCH_ENV_TMP" "$LUMOSWITCH_CODEX_ENV.backup.lumoswitch.$LUMOSWITCH_BACKUP_SUFFIX"
  LUMOSWITCH_ENV_TMP=""
fi

LUMOSWITCH_ENV_TMP="$(mktemp "$LUMOSWITCH_CODEX_DIR/.env.lumoswitch.XXXXXX")"
if [ -f "$LUMOSWITCH_CODEX_ENV" ]; then
  awk '!/^[[:space:]]*(export[[:space:]]+)?LUMOSWITCH_API_KEY[[:space:]]*=/' "$LUMOSWITCH_CODEX_ENV" > "$LUMOSWITCH_ENV_TMP"
fi
printf 'LUMOSWITCH_API_KEY=%s\n' "{{access_key}}" >> "$LUMOSWITCH_ENV_TMP"
chmod 600 "$LUMOSWITCH_ENV_TMP"
mv "$LUMOSWITCH_ENV_TMP" "$LUMOSWITCH_CODEX_ENV"
LUMOSWITCH_ENV_TMP=""

printf 'Shared Codex settings updated; backup suffix: %s\n' "$LUMOSWITCH_BACKUP_SUFFIX"
open -a ChatGPT

The command:

  • backs up ~/.codex/config.toml before updating the default model, Lumoswitch provider, and dedicated model catalog;
  • creates ~/.codex/lumoswitch-model-catalog.json so the model picker contains only the client-facing model from the API configuration result;
  • validates that catalog with the Codex executable bundled in ChatGPT before installing it;
  • installs the capability-aware model catalog produced by Lumoswitch, exposing only reasoning levels supported by both the model and its effective route;
  • applies a cross-model minimum-compatibility profile by setting top-level web_search = "disabled" and merging multi_agent = false into an existing or new [features] table; this disables Codex's multi-agent namespace and native web search so downstream models that do not recognize those tools do not reject the entire Responses request, while standard function tools, terminal commands, and file operations remain available;
  • preserves MCP, approval, plugin, and other provider settings;
  • stores the Access Key in ~/.codex/.env with 0600 permissions instead of putting it in TOML;
  • preserves other .env values in backups while deliberately excluding LUMOSWITCH_API_KEY, so the credential is never copied into a backup;
  • stops without silently overwriting the original when it finds a symbolic link, a non-regular dedicated catalog path, another configured model catalog, or an unsupported TOML form; and
  • opens ChatGPT when configuration finishes.

Native Windows PowerShell import

When ChatGPT uses its default Windows native Agent environment, it shares the user-level Codex settings in %USERPROFILE%\.codex and requires a native PowerShell workflow. If Settings > Agent environment is set to WSL, switch it to Windows native and restart ChatGPT before importing because WSL uses a different Codex home by default. Quit ChatGPT completely, then run this complete command in a trusted PowerShell terminal. A one-time mode is intentionally unavailable because a Start menu app does not reliably inherit process-scoped PowerShell variables.

& {
  $ErrorActionPreference = 'Stop'
  $lumoswitchCodexDir = Join-Path $HOME '.codex'
  $lumoswitchConfig = Join-Path $lumoswitchCodexDir 'config.toml'
  $lumoswitchEnvironment = Join-Path $lumoswitchCodexDir '.env'
  $lumoswitchCatalog = Join-Path $lumoswitchCodexDir 'lumoswitch-model-catalog.json'
  $lumoswitchBackupSuffix = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + $PID

  $lumoswitchChatGptApp = Get-StartApps | Where-Object { $_.Name -eq 'ChatGPT' -or $_.AppID -match 'ChatGPT' } | Select-Object -First 1
  if ($null -eq $lumoswitchChatGptApp) { throw 'ChatGPT was not found in the Start menu. Install or update the Windows desktop app first.' }
  if (Get-Process -Name 'ChatGPT' -ErrorAction SilentlyContinue) { throw 'Quit ChatGPT completely, then run this command again.' }

  function Assert-LumoswitchFile([string] $Path) {
    if (-not (Test-Path -LiteralPath $Path)) { return }
    $lumoswitchItem = Get-Item -LiteralPath $Path -Force
    if ($lumoswitchItem.PSIsContainer -or ($lumoswitchItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
      throw "Lumoswitch refused to replace a directory or link: $Path"
    }
  }

  function Protect-LumoswitchFile([string] $Path) {
    $lumoswitchSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
    & icacls.exe $Path '/inheritance:r' '/grant:r' ('*' + $lumoswitchSid + ':(F)') '*S-1-5-18:(F)' '*S-1-5-32-544:(F)' | Out-Null
    if ($LASTEXITCODE -ne 0) { throw "Could not restrict access to $Path" }
  }

  function Write-LumoswitchFile([string] $Path, [string] $Content) {
    Assert-LumoswitchFile $Path
    [IO.Directory]::CreateDirectory((Split-Path -Parent $Path)) | Out-Null
    $lumoswitchTemporary = $Path + '.' + [Guid]::NewGuid().ToString('N') + '.tmp'
    try {
      [IO.File]::WriteAllText($lumoswitchTemporary, $Content, [Text.UTF8Encoding]::new($false))
      Move-Item -LiteralPath $lumoswitchTemporary -Destination $Path -Force
      Protect-LumoswitchFile $Path
    } finally {
      Remove-Item -LiteralPath $lumoswitchTemporary -Force -ErrorAction SilentlyContinue
    }
  }

  if (Test-Path -LiteralPath $lumoswitchCodexDir) {
    $lumoswitchRootItem = Get-Item -LiteralPath $lumoswitchCodexDir -Force
    if (-not $lumoswitchRootItem.PSIsContainer -or ($lumoswitchRootItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
      throw 'The user-level Codex directory is not a regular directory. Automatic merge stopped.'
    }
  } else {
    [IO.Directory]::CreateDirectory($lumoswitchCodexDir) | Out-Null
  }
  foreach ($lumoswitchPath in @($lumoswitchConfig, $lumoswitchEnvironment, $lumoswitchCatalog)) { Assert-LumoswitchFile $lumoswitchPath }

  $lumoswitchCatalogText = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('{{codex_model_catalog_base64}}'))
  $lumoswitchCatalogObject = $lumoswitchCatalogText | ConvertFrom-Json
  $lumoswitchCatalogModels = @($lumoswitchCatalogObject.models)
  if ($lumoswitchCatalogModels.Count -eq 0 -or @($lumoswitchCatalogModels.slug) -notcontains '{{model}}') {
    throw 'The generated Codex model catalog is empty or does not contain the selected model.'
  }

  $lumoswitchCatalogToml = $lumoswitchCatalog.Replace('\', '/')
  $lumoswitchExpectedCatalog = '"' + $lumoswitchCatalogToml + '"'
  $lumoswitchConfigLines = if (Test-Path -LiteralPath $lumoswitchConfig -PathType Leaf) { @([IO.File]::ReadAllLines($lumoswitchConfig, [Text.Encoding]::UTF8)) } else { @() }
  $lumoswitchRoot = $true
  $lumoswitchFeaturesCount = 0
  foreach ($lumoswitchLine in $lumoswitchConfigLines) {
    if ($lumoswitchLine -match '^\s*\[') {
      $lumoswitchRoot = $false
      $lumoswitchNormalized = $lumoswitchLine -replace '\s', ''
      if ($lumoswitchNormalized -eq '[features]') { $lumoswitchFeaturesCount++ }
      elseif ($lumoswitchNormalized -match '^\[(?:"features"|features\.)') { throw 'Quoted or nested features tables cannot be merged safely.' }
      if ($lumoswitchNormalized -match '^\[model_providers\."lumoswitch"') { throw 'A quoted Lumoswitch provider table cannot be merged safely.' }
    }
    if ($lumoswitchRoot -and $lumoswitchLine -match '^\s*model_providers\s*=') { throw 'Inline model_providers cannot be merged safely.' }
    if ($lumoswitchRoot -and $lumoswitchLine -match '^\s*features(?:\.|\s*=)') { throw 'Inline features settings cannot be merged safely.' }
    if ($lumoswitchRoot -and $lumoswitchLine -match '^\s*model_catalog_json\s*=') {
      $lumoswitchValue = ($lumoswitchLine -replace '^\s*model_catalog_json\s*=\s*', '') -replace '\s*(#.*)?$', ''
      if ($lumoswitchValue -ne $lumoswitchExpectedCatalog) { throw 'Another Codex custom model catalog is already configured.' }
    }
  }
  if ($lumoswitchFeaturesCount -gt 1) { throw 'Duplicate features tables cannot be merged safely.' }

  if (Test-Path -LiteralPath $lumoswitchConfig -PathType Leaf) {
    $lumoswitchConfigBackup = $lumoswitchConfig + '.backup.' + $lumoswitchBackupSuffix
    Assert-LumoswitchFile $lumoswitchConfigBackup
    Copy-Item -LiteralPath $lumoswitchConfig -Destination $lumoswitchConfigBackup
    Protect-LumoswitchFile $lumoswitchConfigBackup
  }

  $lumoswitchOutput = [Collections.Generic.List[string]]::new()
  foreach ($lumoswitchLine in @(
    'model = "{{model}}"',
    'model_provider = "lumoswitch"',
    ('model_catalog_json = "' + $lumoswitchCatalogToml + '"'),
    'web_search = "disabled"',
    ''
  )) { $lumoswitchOutput.Add($lumoswitchLine) | Out-Null }
  $lumoswitchCopyRoot = $true
  $lumoswitchSkip = $false
  $lumoswitchInFeatures = $false
  $lumoswitchFeaturesSeen = $false
  foreach ($lumoswitchLine in $lumoswitchConfigLines) {
    if ($lumoswitchLine -match '^\s*\[') {
      if ($lumoswitchInFeatures) { $lumoswitchOutput.Add('multi_agent = false') | Out-Null }
      $lumoswitchInFeatures = $false
      $lumoswitchCopyRoot = $false
      if ($lumoswitchLine -match '^\s*\[\s*model_providers\.lumoswitch(?:\.[^]]+)?\s*\]\s*$') { $lumoswitchSkip = $true; continue }
      $lumoswitchSkip = $false
      if (($lumoswitchLine -replace '\s', '') -eq '[features]') { $lumoswitchInFeatures = $true; $lumoswitchFeaturesSeen = $true }
    }
    if ($lumoswitchSkip) { continue }
    if ($lumoswitchInFeatures -and $lumoswitchLine -match '^\s*multi_agent\s*=') { continue }
    if ($lumoswitchCopyRoot -and $lumoswitchLine -match '^\s*(model|model_provider|model_catalog_json|model_reasoning_effort|web_search)\s*=') { continue }
    $lumoswitchOutput.Add($lumoswitchLine) | Out-Null
  }
  if ($lumoswitchInFeatures) { $lumoswitchOutput.Add('multi_agent = false') | Out-Null }
  if (-not $lumoswitchFeaturesSeen) {
    $lumoswitchOutput.Add('') | Out-Null
    $lumoswitchOutput.Add('[features]') | Out-Null
    $lumoswitchOutput.Add('multi_agent = false') | Out-Null
  }
  foreach ($lumoswitchLine in @(
    '',
    '[model_providers.lumoswitch]',
    'name = "Lumoswitch"',
    'base_url = "{{api_base_url}}"',
    'env_key = "LUMOSWITCH_API_KEY"',
    'wire_api = "responses"',
    'http_headers = { "X-Lumoswitch-Agent" = "codex-v1" }',
    'requires_openai_auth = false'
  )) { $lumoswitchOutput.Add($lumoswitchLine) | Out-Null }

  $lumoswitchNewLine = [Environment]::NewLine
  Write-LumoswitchFile $lumoswitchCatalog $lumoswitchCatalogText
  Write-LumoswitchFile $lumoswitchConfig (($lumoswitchOutput -join $lumoswitchNewLine) + $lumoswitchNewLine)

  $lumoswitchEnvironmentLines = if (Test-Path -LiteralPath $lumoswitchEnvironment -PathType Leaf) {
    @([IO.File]::ReadAllLines($lumoswitchEnvironment, [Text.Encoding]::UTF8) | Where-Object { $_ -notmatch '^\s*(?:export\s+)?LUMOSWITCH_API_KEY\s*=' })
  } else { @() }
  if (Test-Path -LiteralPath $lumoswitchEnvironment -PathType Leaf) {
    $lumoswitchEnvironmentBackup = $lumoswitchEnvironment + '.backup.lumoswitch.' + $lumoswitchBackupSuffix
    $lumoswitchEnvironmentBackupText = if ($lumoswitchEnvironmentLines.Count -gt 0) { ($lumoswitchEnvironmentLines -join $lumoswitchNewLine) + $lumoswitchNewLine } else { '' }
    Write-LumoswitchFile $lumoswitchEnvironmentBackup $lumoswitchEnvironmentBackupText
  }
  $lumoswitchEnvironmentLines += 'LUMOSWITCH_API_KEY={{access_key}}'
  Write-LumoswitchFile $lumoswitchEnvironment (($lumoswitchEnvironmentLines -join $lumoswitchNewLine) + $lumoswitchNewLine)

  Write-Host ('Shared Codex settings updated; backup suffix: ' + $lumoswitchBackupSuffix)
  Start-Process -FilePath explorer.exe -ArgumentList @('shell:AppsFolder\' + $lumoswitchChatGptApp.AppID)
}

The Windows command discovers the installed ChatGPT Start menu App ID instead of hard-coding a Store package identity. It validates the generated JSON catalog and selected model before writing, uses the same conservative TOML merge rules as macOS, restricts the catalog, configuration, credential, and backup files with Windows ACLs, excludes the Access Key from .env backups, and reopens ChatGPT when configuration finishes.

4. Everyday use

Open ChatGPT normally from the Dock, Finder, or Launchpad on macOS, or from the Start menu on Windows, then enter Codex. On macOS, you can also run:

open -a ChatGPT

You do not need to run codex. If the Lumoswitch URL, Access Key, or client-facing model name changes, replace the placeholders and run the persistent settings command again, then quit and reopen ChatGPT completely.

New tasks use the effective default from the generated catalog. /reasoning lists only verified levels; toggle-style models such as DeepSeek expose one equivalent enabled level.

5. Effect on Codex IDE and CLI

ChatGPT desktop, the Codex IDE extension, and plain Codex CLI all read the user-level ~/.codex directory:

SurfaceReads these settingsHow to start after a change
Codex in ChatGPT desktopYesOpen ChatGPT normally, then enter Codex
Codex IDE extensionYesFully restart the IDE, then begin a new task
Plain Codex CLIYesAfter installing CLI, run codex
Ordinary ChatGPT chatsNoThey continue to use ChatGPT normally

The Codex IDE extension does not need a duplicate configuration. If you install Codex CLI later, it also reads the provider and model written here. Use isolated Codex CLI setup when terminal use must stay separate. Use ChatGPT Desktop + Codex CLI when you deliberately want desktop, CLI, and IDE to share the same defaults.

6. Remove the Lumoswitch setup

macOS

Quit ChatGPT completely with Command + Q and end every running Codex CLI session, then run:

set -e
LUMOSWITCH_CODEX_DIR="$HOME/.codex"
LUMOSWITCH_CODEX_CONFIG="$LUMOSWITCH_CODEX_DIR/config.toml"
LUMOSWITCH_CODEX_ENV="$LUMOSWITCH_CODEX_DIR/.env"
LUMOSWITCH_CODEX_CATALOG="$LUMOSWITCH_CODEX_DIR/lumoswitch-model-catalog.json"
LUMOSWITCH_BACKUP_SUFFIX="$(date +%Y%m%d-%H%M%S)-remove-$$"
LUMOSWITCH_CONFIG_TMP=""
LUMOSWITCH_ENV_TMP=""

lumoswitch_cleanup() {
  [ -z "$LUMOSWITCH_CONFIG_TMP" ] || rm -f "$LUMOSWITCH_CONFIG_TMP"
  [ -z "$LUMOSWITCH_ENV_TMP" ] || rm -f "$LUMOSWITCH_ENV_TMP"
}
trap lumoswitch_cleanup EXIT HUP INT TERM

if pgrep -x ChatGPT >/dev/null 2>&1; then
  echo "Quit ChatGPT completely with Command + Q, then run this command again." >&2
  exit 1
fi

mkdir -p "$LUMOSWITCH_CODEX_DIR"
umask 077

if [ -L "$LUMOSWITCH_CODEX_CONFIG" ] || [ -L "$LUMOSWITCH_CODEX_ENV" ] || [ -L "$LUMOSWITCH_CODEX_CATALOG" ]; then
  echo "A Codex settings, credential, or Lumoswitch model-catalog file is a symbolic link. Automatic removal stopped; edit its target manually." >&2
  exit 1
fi

if [ -e "$LUMOSWITCH_CODEX_CATALOG" ] && [ ! -f "$LUMOSWITCH_CODEX_CATALOG" ]; then
  echo "The Lumoswitch model-catalog path is occupied by a non-regular file. Automatic removal stopped: $LUMOSWITCH_CODEX_CATALOG" >&2
  exit 1
fi

LUMOSWITCH_CATALOG_LITERAL="$(/usr/bin/osascript -l JavaScript - "$LUMOSWITCH_CODEX_CATALOG" <<'LUMOSWITCH_PATH_JXA'
function run(argv) {
  return JSON.stringify(argv[0]);
}
LUMOSWITCH_PATH_JXA
)"

LUMOSWITCH_REMOVE_DEFAULTS=0
if [ -f "$LUMOSWITCH_CODEX_CONFIG" ] && awk '
  BEGIN { root = 1; found = 0 }
  /^[[:space:]]*\[/ { root = 0 }
  root && /^[[:space:]]*model_provider[[:space:]]*=[[:space:]]*"lumoswitch"[[:space:]]*(#.*)?$/ { found = 1 }
  END { exit found ? 0 : 1 }
' "$LUMOSWITCH_CODEX_CONFIG"; then
  LUMOSWITCH_REMOVE_DEFAULTS=1
fi

LUMOSWITCH_REMOVE_CATALOG_SETTING=0
if [ -f "$LUMOSWITCH_CODEX_CONFIG" ] && awk -v expected="$LUMOSWITCH_CATALOG_LITERAL" '
  BEGIN { root = 1; found = 0; mismatch = 0 }
  /^[[:space:]]*\[/ { root = 0 }
  root && /^[[:space:]]*model_catalog_json[[:space:]]*=/ {
    value = $0
    sub(/^[[:space:]]*model_catalog_json[[:space:]]*=[[:space:]]*/, "", value)
    sub(/[[:space:]]*(#.*)?$/, "", value)
    if (value == expected) found = 1
    if (value != expected) mismatch = 1
  }
  END { exit found && !mismatch ? 0 : 1 }
' "$LUMOSWITCH_CODEX_CONFIG"; then
  LUMOSWITCH_REMOVE_CATALOG_SETTING=1
fi

if [ -f "$LUMOSWITCH_CODEX_CONFIG" ]; then
  cp "$LUMOSWITCH_CODEX_CONFIG" "$LUMOSWITCH_CODEX_CONFIG.backup.$LUMOSWITCH_BACKUP_SUFFIX"
  chmod 600 "$LUMOSWITCH_CODEX_CONFIG.backup.$LUMOSWITCH_BACKUP_SUFFIX"
  LUMOSWITCH_CONFIG_TMP="$(mktemp "$LUMOSWITCH_CODEX_DIR/config.toml.lumoswitch-remove.XXXXXX")"
  awk -v remove_defaults="$LUMOSWITCH_REMOVE_DEFAULTS" -v remove_catalog="$LUMOSWITCH_REMOVE_CATALOG_SETTING" '
    BEGIN { root = 1; skip = 0; in_features = 0 }
    /^[[:space:]]*\[/ {
      root = 0
      in_features = ($0 ~ /^[[:space:]]*\[[[:space:]]*features[[:space:]]*\]/)
      if ($0 ~ /^[[:space:]]*\[[[:space:]]*model_providers[.]lumoswitch([.][^]]+)?[[:space:]]*\]/) {
        skip = 1
        next
      }
      skip = 0
    }
    skip { next }
    in_features && remove_defaults == "1" && /^[[:space:]]*multi_agent[[:space:]]*=[[:space:]]*false[[:space:]]*(#.*)?$/ { next }
    root && remove_defaults == "1" && /^[[:space:]]*(model|model_provider|model_reasoning_effort)[[:space:]]*=/ { next }
    root && remove_defaults == "1" && /^[[:space:]]*web_search[[:space:]]*=[[:space:]]*"disabled"[[:space:]]*(#.*)?$/ { next }
    root && remove_catalog == "1" && /^[[:space:]]*model_catalog_json[[:space:]]*=/ { next }
    { print }
  ' "$LUMOSWITCH_CODEX_CONFIG" > "$LUMOSWITCH_CONFIG_TMP"
  chmod 600 "$LUMOSWITCH_CONFIG_TMP"
  mv "$LUMOSWITCH_CONFIG_TMP" "$LUMOSWITCH_CODEX_CONFIG"
  LUMOSWITCH_CONFIG_TMP=""
fi

if [ -f "$LUMOSWITCH_CODEX_CATALOG" ]; then
  rm -f "$LUMOSWITCH_CODEX_CATALOG"
fi

if [ -f "$LUMOSWITCH_CODEX_ENV" ]; then
  LUMOSWITCH_ENV_TMP="$(mktemp "$LUMOSWITCH_CODEX_DIR/.env.backup.lumoswitch.XXXXXX")"
  awk '!/^[[:space:]]*(export[[:space:]]+)?LUMOSWITCH_API_KEY[[:space:]]*=/' "$LUMOSWITCH_CODEX_ENV" > "$LUMOSWITCH_ENV_TMP"
  chmod 600 "$LUMOSWITCH_ENV_TMP"
  mv "$LUMOSWITCH_ENV_TMP" "$LUMOSWITCH_CODEX_ENV.backup.lumoswitch.$LUMOSWITCH_BACKUP_SUFFIX"
  LUMOSWITCH_ENV_TMP=""
  LUMOSWITCH_ENV_TMP="$(mktemp "$LUMOSWITCH_CODEX_DIR/.env.lumoswitch-remove.XXXXXX")"
  awk '!/^[[:space:]]*(export[[:space:]]+)?LUMOSWITCH_API_KEY[[:space:]]*=/' "$LUMOSWITCH_CODEX_ENV" > "$LUMOSWITCH_ENV_TMP"
  chmod 600 "$LUMOSWITCH_ENV_TMP"
  mv "$LUMOSWITCH_ENV_TMP" "$LUMOSWITCH_CODEX_ENV"
  LUMOSWITCH_ENV_TMP=""
fi

printf 'Lumoswitch Codex setup removed; pre-removal backup suffix: %s\n' "$LUMOSWITCH_BACKUP_SUFFIX"
open -a ChatGPT

The command first creates a safe backup that omits the Lumoswitch Key, then removes only Lumoswitch-related settings. If the top-level model_provider still points to lumoswitch, it also removes top-level model, model_provider, and any model_reasoning_effort left by an earlier version of this guide. It removes web_search only while it still equals "disabled", and removes [features].multi_agent only while it still equals false; later user changes and every other feature entry remain intact, and an empty [features] table may remain. It removes top-level model_catalog_json only when its value exactly matches the dedicated absolute path, and deletes only the regular ~/.codex/lumoswitch-model-catalog.json file. Other catalog paths are left unchanged. Symbolic links and non-regular dedicated catalog paths stop automatic removal and are never followed.

Automatic removal cannot know which default model and provider were overwritten before setup. If you need an exact restoration and no later settings changed, manually restore the config.toml file with the same backup suffix printed by the installation command; do not blindly restore the newest backup. The Access Key is deliberately excluded from backups and must be entered again when reconnecting.

Windows PowerShell

Quit ChatGPT completely and end every running Codex CLI session, then run:

& {
  $ErrorActionPreference = 'Stop'
  $lumoswitchCodexDir = Join-Path $HOME '.codex'
  $lumoswitchConfig = Join-Path $lumoswitchCodexDir 'config.toml'
  $lumoswitchEnvironment = Join-Path $lumoswitchCodexDir '.env'
  $lumoswitchCatalog = Join-Path $lumoswitchCodexDir 'lumoswitch-model-catalog.json'
  $lumoswitchBackupSuffix = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-remove-' + $PID

  if (Get-Process -Name 'ChatGPT' -ErrorAction SilentlyContinue) { throw 'Quit ChatGPT completely, then run this command again.' }
  $lumoswitchChatGptApp = Get-StartApps | Where-Object { $_.Name -eq 'ChatGPT' -or $_.AppID -match 'ChatGPT' } | Select-Object -First 1

  function Assert-LumoswitchFile([string] $Path) {
    if (-not (Test-Path -LiteralPath $Path)) { return }
    $lumoswitchItem = Get-Item -LiteralPath $Path -Force
    if ($lumoswitchItem.PSIsContainer -or ($lumoswitchItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
      throw "Lumoswitch refused to modify a directory or link: $Path"
    }
  }

  function Protect-LumoswitchFile([string] $Path) {
    $lumoswitchSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
    & icacls.exe $Path '/inheritance:r' '/grant:r' ('*' + $lumoswitchSid + ':(F)') '*S-1-5-18:(F)' '*S-1-5-32-544:(F)' | Out-Null
    if ($LASTEXITCODE -ne 0) { throw "Could not restrict access to $Path" }
  }

  function Write-LumoswitchFile([string] $Path, [string] $Content) {
    Assert-LumoswitchFile $Path
    [IO.Directory]::CreateDirectory((Split-Path -Parent $Path)) | Out-Null
    $lumoswitchTemporary = $Path + '.' + [Guid]::NewGuid().ToString('N') + '.tmp'
    try {
      [IO.File]::WriteAllText($lumoswitchTemporary, $Content, [Text.UTF8Encoding]::new($false))
      Move-Item -LiteralPath $lumoswitchTemporary -Destination $Path -Force
      Protect-LumoswitchFile $Path
    } finally {
      Remove-Item -LiteralPath $lumoswitchTemporary -Force -ErrorAction SilentlyContinue
    }
  }

  if (Test-Path -LiteralPath $lumoswitchCodexDir) {
    $lumoswitchRootItem = Get-Item -LiteralPath $lumoswitchCodexDir -Force
    if (-not $lumoswitchRootItem.PSIsContainer -or ($lumoswitchRootItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
      throw 'The user-level Codex directory is not a regular directory. Automatic removal stopped.'
    }
  } else {
    [IO.Directory]::CreateDirectory($lumoswitchCodexDir) | Out-Null
  }
  foreach ($lumoswitchPath in @($lumoswitchConfig, $lumoswitchEnvironment, $lumoswitchCatalog)) { Assert-LumoswitchFile $lumoswitchPath }

  $lumoswitchNewLine = [Environment]::NewLine
  $lumoswitchConfigLines = if (Test-Path -LiteralPath $lumoswitchConfig -PathType Leaf) { @([IO.File]::ReadAllLines($lumoswitchConfig, [Text.Encoding]::UTF8)) } else { @() }
  $lumoswitchExpectedCatalog = '"' + $lumoswitchCatalog.Replace('\', '/') + '"'
  $lumoswitchRemoveDefaults = $false
  $lumoswitchCatalogSettings = 0
  $lumoswitchCatalogMatches = 0
  $lumoswitchRoot = $true
  foreach ($lumoswitchLine in $lumoswitchConfigLines) {
    if ($lumoswitchLine -match '^\s*\[') { $lumoswitchRoot = $false }
    if ($lumoswitchRoot -and $lumoswitchLine -match '^\s*model_provider\s*=\s*"lumoswitch"\s*(?:#.*)?$') { $lumoswitchRemoveDefaults = $true }
    if ($lumoswitchRoot -and $lumoswitchLine -match '^\s*model_catalog_json\s*=') {
      $lumoswitchCatalogSettings++
      $lumoswitchValue = ($lumoswitchLine -replace '^\s*model_catalog_json\s*=\s*', '') -replace '\s*(#.*)?$', ''
      if ($lumoswitchValue -eq $lumoswitchExpectedCatalog) { $lumoswitchCatalogMatches++ }
    }
  }
  $lumoswitchRemoveCatalog = ($lumoswitchCatalogSettings -eq 1 -and $lumoswitchCatalogMatches -eq 1)

  if (Test-Path -LiteralPath $lumoswitchConfig -PathType Leaf) {
    $lumoswitchConfigBackup = $lumoswitchConfig + '.backup.' + $lumoswitchBackupSuffix
    Assert-LumoswitchFile $lumoswitchConfigBackup
    Copy-Item -LiteralPath $lumoswitchConfig -Destination $lumoswitchConfigBackup
    Protect-LumoswitchFile $lumoswitchConfigBackup

    $lumoswitchOutput = [Collections.Generic.List[string]]::new()
    $lumoswitchCopyRoot = $true
    $lumoswitchSkip = $false
    $lumoswitchInFeatures = $false
    foreach ($lumoswitchLine in $lumoswitchConfigLines) {
      if ($lumoswitchLine -match '^\s*\[') {
        $lumoswitchCopyRoot = $false
        $lumoswitchInFeatures = (($lumoswitchLine -replace '\s', '') -eq '[features]')
        if ($lumoswitchLine -match '^\s*\[\s*model_providers\.lumoswitch(?:\.[^]]+)?\s*\]\s*$') { $lumoswitchSkip = $true; continue }
        $lumoswitchSkip = $false
      }
      if ($lumoswitchSkip) { continue }
      if ($lumoswitchInFeatures -and $lumoswitchRemoveDefaults -and $lumoswitchLine -match '^\s*multi_agent\s*=\s*false\s*(?:#.*)?$') { continue }
      if ($lumoswitchCopyRoot -and $lumoswitchRemoveDefaults -and $lumoswitchLine -match '^\s*(?:model|model_provider|model_reasoning_effort)\s*=') { continue }
      if ($lumoswitchCopyRoot -and $lumoswitchRemoveDefaults -and $lumoswitchLine -match '^\s*web_search\s*=\s*"disabled"\s*(?:#.*)?$') { continue }
      if ($lumoswitchCopyRoot -and $lumoswitchRemoveCatalog -and $lumoswitchLine -match '^\s*model_catalog_json\s*=') { continue }
      $lumoswitchOutput.Add($lumoswitchLine) | Out-Null
    }
    $lumoswitchConfigText = if ($lumoswitchOutput.Count -gt 0) { ($lumoswitchOutput -join $lumoswitchNewLine) + $lumoswitchNewLine } else { '' }
    Write-LumoswitchFile $lumoswitchConfig $lumoswitchConfigText
  }

  if ($lumoswitchRemoveCatalog -and (Test-Path -LiteralPath $lumoswitchCatalog -PathType Leaf)) {
    Remove-Item -LiteralPath $lumoswitchCatalog -Force
  }

  if (Test-Path -LiteralPath $lumoswitchEnvironment -PathType Leaf) {
    $lumoswitchEnvironmentLines = @([IO.File]::ReadAllLines($lumoswitchEnvironment, [Text.Encoding]::UTF8) | Where-Object { $_ -notmatch '^\s*(?:export\s+)?LUMOSWITCH_API_KEY\s*=' })
    $lumoswitchEnvironmentText = if ($lumoswitchEnvironmentLines.Count -gt 0) { ($lumoswitchEnvironmentLines -join $lumoswitchNewLine) + $lumoswitchNewLine } else { '' }
    $lumoswitchEnvironmentBackup = $lumoswitchEnvironment + '.backup.lumoswitch.' + $lumoswitchBackupSuffix
    Write-LumoswitchFile $lumoswitchEnvironmentBackup $lumoswitchEnvironmentText
    Write-LumoswitchFile $lumoswitchEnvironment $lumoswitchEnvironmentText
  }

  Write-Host ('Lumoswitch Codex setup removed; pre-removal backup suffix: ' + $lumoswitchBackupSuffix)
  if ($null -ne $lumoswitchChatGptApp) {
    Start-Process -FilePath explorer.exe -ArgumentList @('shell:AppsFolder\' + $lumoswitchChatGptApp.AppID)
  }
}

The Windows command applies the same conservative cleanup rules to %USERPROFILE%\.codex, creates ACL-restricted backups that omit the Access Key, and reopens ChatGPT through its discovered Start menu App ID when it is still installed.

7. Verify the connection

Open the Codex workspace in ChatGPT desktop. Before sending a request, open the model picker: it should contain the client-facing Lumoswitch model, not the built-in GPT catalog. Begin a new task, send a small text request, and then find that request in Lumoswitch logs.

If Codex does not respond, check that:

  • you opened a Codex task, not an ordinary ChatGPT conversation;
  • the selected model is the client-facing model shown on the Lumoswitch configuration result;
  • the API configuration enables Responses output;
  • the API URL ends in /v1 but not /responses;
  • the client-facing model name matches the configuration result exactly; and
  • you fully quit and reopened ChatGPT after writing the settings.

The generated catalog advertises text-only input and only the reasoning levels verified for the model's effective Lumoswitch route. Models without verified request-side reasoning control use none and expose no selector levels.

Manual configuration when automatic merging stops

If no other top-level model_catalog_json is configured, create ~/.codex/lumoswitch-model-catalog.json with the following content, replacing both model-name values:

{
  "models": [
    {
      "slug": "your-client-facing-model-name",
      "display_name": "your-client-facing-model-name",
      "description": "Lumoswitch downstream model",
      "default_reasoning_level": "none",
      "supported_reasoning_levels": [],
      "supports_reasoning_summary_parameter": false,
      "default_reasoning_summary": "none",
      "shell_type": "shell_command",
      "visibility": "list",
      "supported_in_api": true,
      "priority": 0,
      "base_instructions": "You are a coding agent. Follow the user's instructions and use the available tools when needed.",
      "support_verbosity": false,
      "truncation_policy": { "mode": "tokens", "limit": 10000 },
      "supports_parallel_tool_calls": false,
      "experimental_supported_tools": [],
      "input_modalities": ["text"]
    }
  ]
}

In ChatGPT, open Settings > Configuration > Open config.toml and merge the following values into the user-level ~/.codex/config.toml. Replace /Users/your-name with your real home-directory path. Edit existing top-level keys, [features], or provider tables instead of declaring duplicates. This compatibility profile disables Codex's multi-agent namespace and native web search, while standard function tools, terminal commands, and file operations remain available:

model = "your-client-facing-model-name"
model_provider = "lumoswitch"
model_catalog_json = "/Users/your-name/.codex/lumoswitch-model-catalog.json"
web_search = "disabled"

[features]
multi_agent = false

[model_providers.lumoswitch]
name = "Lumoswitch"
base_url = "https://api.lumoswitch.com/v1"
env_key = "LUMOSWITCH_API_KEY"
wire_api = "responses"
http_headers = { "X-Lumoswitch-Agent" = "codex-v1" }
requires_openai_auth = false

Then store the credential in ~/.codex/.env:

LUMOSWITCH_API_KEY="your-lumoswitch-access-key"

If config.toml already points to another custom model catalog, do not replace that path. Add the Lumoswitch model object to the existing catalog instead, retain its original model_catalog_json value, and validate the resulting catalog with the Codex executable bundled in ChatGPT. Do not remove unrelated settings or commit .env to Git. Quit and reopen ChatGPT completely after editing.

Publication fields
  • Platform ID: chatgpt-desktop
  • Display name: ChatGPT Desktop (Codex)
  • Downstream protocol: responses
  • macOS/Linux temporary mode: disabled; leave commandTemplate empty
  • macOS persistence strategy: native
  • persistentCommandTemplate: copy the complete macOS Persistent use code block from this page
  • macOS futureCommand: open -a ChatGPT
  • Windows temporary mode: disabled; leave windowsCommandTemplate empty
  • Windows persistence strategy: native
  • windowsPersistentCommandTemplate: copy the complete Native Windows PowerShell import code block from this page
  • Windows windowsFutureCommand: null; reopen from the Start menu
  • Sort order: 15
  • Publication limit: publish the macOS and Windows templates only for their marked operating systems
  • Verification status: the official configuration contract was revalidated on 2026-08-27; model-catalog parsing was last validated with the Codex executable bundled in ChatGPT on 2026-08-03; Windows PowerShell is covered by structural and bilingual-template contract tests, while a real Windows Lumoswitch traffic check is still pending

Troubleshooting

  • Terminal reports codex: command not found: This setup does not require CLI. You can ignore the message and do not need to install CLI for desktop use.
  • Ordinary ChatGPT chats do not use Lumoswitch: This is expected. Test inside the Codex workspace.
  • The picker still shows only GPT models: Quit ChatGPT completely, rerun the latest persistent-use command, and reopen ChatGPT. Confirm that root model_catalog_json points to the dedicated absolute path and that the generated JSON contains the exact client-facing model name.
  • Installation reports another custom model catalog: The command stops to protect that catalog. Add the Lumoswitch model entry to the existing JSON manually and keep its current model_catalog_json path.
  • The runtime says the inference request is invalid: Confirm the selected model and Responses output first, then rerun one-click import to refresh the capability-aware catalog. Use the matching Lumoswitch log to identify any remaining rejected field.
  • You need to restore the old settings: Use the backup suffix printed by the command and copy the matching .backup.<suffix> files back to their original paths.

References: ChatGPT desktop for Windows, Codex custom model providers, Codex configuration reference, Codex model listing, and desktop and IDE environment variables.

On this page