Repository navigation
fix: harden native codex queue fallback for the desktop composer #619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 3 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
fbda967
docs: document native codex queue fallback for the desktop composer u…
luvs01 d7375b0
ci: retrigger macos 1/2 after 20-minute stall mid-shard
devin-ai-integration[bot] 45ff4f9
fix: harden native Codex queue fallback and document dispatch limits
luvs01 c938967
fix: preserve queue integration context and private diagnostics
luvs01 59bf48c
fix: anchor Windows queue homes and correct native test assertions
luvs01 b8cfba1
fix: discover queue-capable Codex across every PATH entry
luvs01 3c1bffd
test: cover Codex queue argument escaping, session selection, private…
coderabbitai[bot] eaf5b0b
docs: document Codex queue helper functions in Bash and PowerShell
coderabbitai[bot] eaac13e
docs: document Codex queue test helper functions
coderabbitai[bot] 9efc3c6
fix: require absolute PATH entries for automatic queue CLI discovery
luvs01 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
137 changes: 137 additions & 0 deletions
137
docs-site/src/content/docs/guides/composer-usage-gate-fallback.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| --- | ||
| title: Composer Usage-Gate Fallback | ||
| description: Queue text to an existing Codex thread without changing desktop authentication, while preserving server-side quotas and making delivery limits explicit. | ||
| --- | ||
|
|
||
| A desktop **composer-only** usage gate can prevent new input even when a thread's configured | ||
| OpenCodex route has available capacity. On a compatible installation, `codex queue` is a | ||
| low-impact fallback: it submits through Codex's native app-server queue without using that input | ||
| box. It does not patch the app, intercept TLS, install a certificate, or change authentication. | ||
|
|
||
| This is **not a fix for every usage-limit state**. It neither restores exhausted quota nor | ||
| unlocks the model picker. A thread already using `gpt-reserve` keeps that model; queueing text | ||
| does not switch it to another provider. Server-side authorization, provider quotas, approvals, | ||
| and the thread's execution settings still apply. | ||
|
|
||
| ## Check the target and compatibility first | ||
|
|
||
| Use the **same `CODEX_HOME` and a compatible CLI/app-server** as the target desktop installation. | ||
| `CODEX_HOME` defaults to `~/.codex`; a different home can discover a different daemon and thread | ||
| store. Do not change it just to bypass an error. Prefer the app-bundled CLI; a separately installed | ||
| `codex` on PATH may be older. Check `codex queue --help` for `--thread` and `--message`. | ||
|
|
||
| The helpers below probe this CLI capability, but only the actual request can verify the daemon's | ||
| `thread/queue/add` support. If Codex reports an unsupported queue method, save ongoing work before | ||
| updating/restarting the matching installation. The helpers do not restart a daemon, change | ||
| settings, or retry with another server. Do not add `--no-daemon` to a queue command. | ||
|
|
||
| ## Sending to an existing thread | ||
|
|
||
| First open the intended conversation in the desktop app and confirm its project, model and | ||
| provider. Prefer its **explicit UUID**; the native CLI also accepts an exact session name: | ||
|
|
||
| ```powershell | ||
| codex queue --thread 'my-project-review' --message 'continue with the next step' | ||
| ``` | ||
|
|
||
| From a repository checkout, the Windows helper can discover the bundled native `codex.exe`: | ||
|
|
||
| ```powershell | ||
| .\scripts\codex-queue.ps1 -Thread 'my-project-review' -Message 'continue with the next step' | ||
| ``` | ||
|
|
||
| On macOS/Linux, use Bash (including macOS's Bash 3.2): | ||
|
|
||
| ```bash | ||
| bash scripts/codex-queue.sh --thread 'my-project-review' --message 'continue with the next step' | ||
| ``` | ||
|
|
||
| To pin the matching trusted binary, pass `-CodexExe 'C:\path\to\codex.exe'` or | ||
| `--codex '/path/to/codex'`. Both helpers also accept `CODEX_EXE` as a path override. | ||
| An invalid explicit selection fails instead of silently picking another executable. On Windows, | ||
| use a native `.exe`, not an npm `.cmd` or PowerShell shim; this keeps message quoting out of | ||
| `cmd.exe`. Bundled and standalone package layouts are tried before PATH, and a candidate that | ||
| lacks the queue flags is skipped. Discovery is best-effort, not proof of a matching app version. | ||
|
|
||
| ### Optional latest-thread discovery | ||
|
|
||
| Neither helper silently selects a thread when the target is omitted. For legacy rollout-based | ||
| stores, inspect an explicit **latest-file heuristic** with: | ||
|
|
||
| ```powershell | ||
| .\scripts\codex-queue.ps1 -Latest -DryRun | ||
| ``` | ||
|
|
||
| ```bash | ||
| bash scripts/codex-queue.sh --latest --dry-run | ||
| ``` | ||
|
|
||
| This searches `CODEX_HOME/sessions` (or `~/.codex/sessions` by default) by modification time; | ||
| filename order breaks equal-time ties. It is **not the current desktop conversation** and can | ||
| select a different project or a subagent. Verify the preview, then use `-Thread` / `--thread` | ||
| with the chosen UUID. `-Latest` / `--latest` can also send when a message is provided, but that | ||
| remains an explicit opt-in to this heuristic. | ||
|
|
||
| Recognized `rollout-*.jsonl` filenames contain a thread UUID, sometimes followed by | ||
| `_<rollout-uuid>`; the helper uses the first UUID. Malformed names are skipped, and missing or | ||
| unreadable stores fail rather than falling back to another home. Migrated/paginated-only stores | ||
| and remote-only conversations may have no matching local rollout: use the explicit UUID/name | ||
| instead. `-DryRun` / `--dry-run` only probes CLI help and prints the chosen executable and target; | ||
| it never queues or prints the message body. | ||
|
|
||
| Keep the entire message in one argument. Bash accepts `--message '- start with this'` or | ||
| `-- '- start with this'`; PowerShell accepts `-Message '- start with this'`. Shell history and | ||
| local process listings can expose command-line text, so do not include credentials in prompts. | ||
|
|
||
| ## Queued is not the same as executed | ||
|
|
||
| `Queued message ... for thread ...` confirms **queue acceptance**, not model execution or | ||
| completion. A busy thread may wait for its current turn or approval. In the inspected upstream | ||
| implementation, an unloaded saved thread can retain the message without dispatching it until | ||
| another client resumes that thread. | ||
|
|
||
| Check the queue and activity in the **same conversation**. If it is not loaded, open it in the | ||
| app or use `codex resume <thread-id>` without adding the prompt again. Review pending approvals | ||
| and the thread's queue state. Do not repeatedly re-send a message just because no response has | ||
| appeared: each queue invocation can create another queued item. The helpers propagate the CLI | ||
| exit status and never retry automatically; after an ambiguous failure, inspect before retrying. | ||
|
|
||
| The helpers target local discovery; they do not identify a conversation on another machine from | ||
| local filenames. The native CLI has explicit `--remote` options (see `codex queue --help`), but | ||
| that is distinct from assuming the desktop's remote-control connection is automatically reused. | ||
| This workaround leaves desktop authentication configuration alone; it does not guarantee that an | ||
| unrelated authentication/network fault or unsupported server will be repaired. | ||
|
|
||
| ## Starting or resuming work without the composer | ||
|
|
||
| `codex exec '<prompt>'` starts a non-interactive task; it is **not** delivery into the currently | ||
| open desktop thread. Check its working directory, provider, permissions and configuration. | ||
| `codex resume <thread-id>` resumes an explicit existing session. `codex resume --last` normally | ||
| filters selection by the current working directory; `--all` disables that filter, while other | ||
| session eligibility filters can still apply. A global selection is not necessarily the visible | ||
| or newest filesystem session. Prefer an explicit ID for ongoing desktop work. | ||
|
|
||
| ## Scope and verification | ||
|
|
||
| This fallback is preferable to changing feature-gate responses or authentication solely to get | ||
| text into an otherwise usable thread: it changes neither account entitlement nor app files. | ||
| It is a workaround, not a provider-aware repair of the desktop composer/model picker. App/CLI | ||
| updates can still change compatibility. When the composer becomes usable, no helper-specific | ||
| configuration needs reverting; these scripts do not undo unrelated earlier proxy/certificate | ||
| changes. | ||
|
luvs01 marked this conversation as resolved.
Outdated
|
||
|
|
||
| The original Windows probe (desktop `26.917.9434.0`) reported queue acceptance for a live thread | ||
| and an expected error for a nonexistent thread. That is not a general end-to-end inference, | ||
| unloaded-thread, remote-control or cross-platform guarantee. The current upstream source was | ||
| also checked at commit `7dae8c53d97e61cd774e4d6bcca5243c29ca615c`: | ||
|
|
||
| - [CLI queue options](https://github.com/openai/codex/blob/7dae8c53d97e61cd774e4d6bcca5243c29ca615c/codex-rs/cli/src/queue_cmd.rs) | ||
| and [app-server submission](https://github.com/openai/codex/blob/7dae8c53d97e61cd774e4d6bcca5243c29ca615c/codex-rs/tui/src/session_queue_commands.rs). | ||
| - [Loaded-thread queue dispatch](https://github.com/openai/codex/blob/7dae8c53d97e61cd774e4d6bcca5243c29ca615c/codex-rs/ext/queue/src/service.rs) | ||
| and [resume selection options](https://github.com/openai/codex/blob/7dae8c53d97e61cd774e4d6bcca5243c29ca615c/codex-rs/cli/src/main.rs). | ||
|
|
||
| Maintainers can run the offline wrapper regressions with | ||
| `node --test scripts/codex-queue.test.mjs` (Node 20+). They use a fake native CLI and temporary | ||
| homes, never a real account or model. Windows runs Windows PowerShell and, when installed, pwsh; | ||
| POSIX runs Bash and, when installed, pwsh. These tests do not establish live queue dispatch or | ||
| Desktop compatibility; validate those separately on the supported installations. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| #Requires -Version 5.1 | ||
| <# | ||
| .SYNOPSIS | ||
| Queue one text message with the native Codex CLI; no auth/config/app changes. | ||
| .DESCRIPTION | ||
| Requires an explicit -Thread or opt-in -Latest. CODEX_HOME is honored. | ||
| Latest means filesystem activity, not the foreground chat. Inspect -DryRun | ||
| first, then prefer -Thread. DryRun probes queue help but never sends a message. | ||
| Windows requires a native codex.exe, not a .cmd/.ps1 shim. | ||
| .EXAMPLE | ||
| .\codex-queue.ps1 -Thread <id-or-exact-name> -Message 'continue' | ||
| .EXAMPLE | ||
| .\codex-queue.ps1 -Latest -DryRun | ||
| #> | ||
| [CmdletBinding()] | ||
| param( | ||
| [Parameter(Position=0)][string]$Message = '', | ||
| [string]$Thread = '', | ||
| [switch]$Latest, | ||
| [switch]$DryRun, | ||
| [string]$CodexExe = $env:CODEX_EXE | ||
| ) | ||
| Set-StrictMode -Version 2.0 | ||
| $ErrorActionPreference = 'Stop' | ||
|
|
||
| function ConvertTo-NativeArgument([string]$Value) { | ||
| # Windows CRT quoting for .NET Framework / Windows PowerShell 5.1: | ||
| # double backslashes before a quote and before the closing quote. | ||
| $escaped = [regex]::Replace($Value, '(\\*)"', '$1$1\"') | ||
| $escaped = [regex]::Replace($escaped, '(\\+)$', '$1$1') | ||
| return '"' + $escaped + '"' | ||
| } | ||
|
|
||
| function Invoke-CodexNative([string]$Exe, [string[]]$Arguments, [switch]$Probe) { | ||
| # Avoid cmd.exe and PowerShell's legacy native-argument serialization: quotes, | ||
| # Unicode, newlines, trailing slashes and shell metacharacters must stay data. | ||
| $info = New-Object System.Diagnostics.ProcessStartInfo | ||
| $info.FileName = $Exe | ||
| $info.UseShellExecute = $false | ||
| if ($null -ne $info.PSObject.Properties['ArgumentList']) { | ||
| foreach ($argument in $Arguments) { $info.ArgumentList.Add($argument) } | ||
| } else { | ||
| $info.Arguments = (($Arguments | ForEach-Object { ConvertTo-NativeArgument $_ }) -join ' ') | ||
| } | ||
| $info.RedirectStandardOutput = [bool]$Probe | ||
| $info.RedirectStandardError = [bool]$Probe | ||
| $process = New-Object System.Diagnostics.Process | ||
| $process.StartInfo = $info | ||
| try { | ||
| [void]$process.Start() | ||
| if ($Probe) { | ||
| $stdout = $process.StandardOutput.ReadToEndAsync() | ||
| $stderr = $process.StandardError.ReadToEndAsync() | ||
| if (-not $process.WaitForExit(10000)) { | ||
| $process.Kill() | ||
| throw 'Codex queue help timed out; select the matching app-bundled CLI with -CodexExe.' | ||
| } | ||
| $process.WaitForExit() | ||
| return [pscustomobject]@{ ExitCode = $process.ExitCode; Output = $stdout.Result } | ||
| } | ||
| $process.WaitForExit() | ||
| return $process.ExitCode | ||
| } finally { | ||
| $process.Dispose() | ||
| } | ||
| } | ||
|
|
||
| function Test-CodexQueue([string]$Path) { | ||
| # A successful generic help response is not enough: check queue-specific flags. | ||
| if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $false } | ||
| if ($env:OS -eq 'Windows_NT' -and [IO.Path]::GetExtension($Path) -ine '.exe') { return $false } | ||
| try { | ||
| $nativePath = (Get-Item -LiteralPath $Path).FullName | ||
| $probe = Invoke-CodexNative $nativePath @('queue', '--help') -Probe | ||
| return $probe.ExitCode -eq 0 -and $probe.Output.Contains('--thread') -and $probe.Output.Contains('--message') | ||
| } catch { return $false } | ||
| } | ||
|
|
||
| function Resolve-CodexExe([string]$Explicit, [string]$CodexHomeDir) { | ||
| # Pinning is authoritative: do not fall back after an invalid explicit choice. | ||
| if (-not [string]::IsNullOrEmpty($Explicit)) { | ||
| if (-not (Test-CodexQueue $Explicit)) { | ||
| throw 'Selected CLI does not support queue --thread/--message. On Windows, -CodexExe must name a native .exe, not a command shim.' | ||
| } | ||
| return (Get-Item -LiteralPath $Explicit).FullName | ||
| } | ||
| $candidates = New-Object 'System.Collections.Generic.List[string]' | ||
| if ($env:LOCALAPPDATA) { | ||
| $binRoot = Join-Path $env:LOCALAPPDATA 'OpenAI\Codex\bin' | ||
| $bundled = @(Get-ChildItem -LiteralPath $binRoot -Directory -ErrorAction SilentlyContinue | | ||
| ForEach-Object { Get-Item -LiteralPath (Join-Path $_.FullName 'codex.exe') -ErrorAction SilentlyContinue } | | ||
| Sort-Object LastWriteTimeUtc, FullName -Descending) | ||
| foreach ($file in $bundled) { $candidates.Add($file.FullName) } | ||
| } | ||
| foreach ($root in @($CodexHomeDir, (Join-Path $HOME '.codex'))) { | ||
| $candidates.Add((Join-Path $root 'packages\standalone\current\bin\codex.exe')) | ||
| $candidates.Add((Join-Path $root 'packages\standalone\current\codex.exe')) | ||
| } | ||
| foreach ($name in @('codex.exe', 'codex')) { | ||
| foreach ($command in @(Get-Command $name -CommandType Application -All -ErrorAction SilentlyContinue)) { | ||
| $candidates.Add($command.Source) | ||
| } | ||
| } | ||
| foreach ($candidate in $candidates) { | ||
| if (Test-CodexQueue $candidate) { return (Get-Item -LiteralPath $candidate).FullName } | ||
| } | ||
| throw 'No queue-capable native Codex CLI found. Install/update Codex or supply -CodexExe.' | ||
| } | ||
|
|
||
| function Resolve-LatestThread([string]$CodexHomeDir) { | ||
| # Scan only the effective store. Fail on incomplete reads instead of silently | ||
| # selecting from another account/home. Deterministic filename order breaks ties. | ||
| $sessions = Join-Path $CodexHomeDir 'sessions' | ||
| if (-not (Test-Path -LiteralPath $sessions -PathType Container)) { | ||
| throw 'No sessions directory under the effective CODEX_HOME.' | ||
| } | ||
| $uuid = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}' | ||
| $pattern = '^rollout-.+-(' + $uuid + ')(_' + $uuid + ')?\.jsonl$' | ||
| $latestFile = Get-ChildItem -LiteralPath $sessions -Recurse -File -Filter 'rollout-*.jsonl' | | ||
| Where-Object { $_.Name -match $pattern } | | ||
| Sort-Object LastWriteTimeUtc, FullName -Descending | | ||
| Select-Object -First 1 | ||
| if ($null -eq $latestFile -or $latestFile.Name -notmatch $pattern) { | ||
| throw 'No recognized rollout thread found; specify -Thread explicitly.' | ||
| } | ||
| return $Matches[1] | ||
| } | ||
|
|
||
| try { | ||
| if ([string]::IsNullOrEmpty($Thread) -and -not $Latest) { | ||
| throw 'Choose -Thread <id-or-exact-name> or explicitly opt in with -Latest.' | ||
| } | ||
| if (-not [string]::IsNullOrEmpty($Thread) -and $Latest) { throw '-Thread and -Latest are mutually exclusive.' } | ||
| if (-not $DryRun -and [string]::IsNullOrEmpty($Message)) { throw 'A nonempty message is required.' } | ||
| if ($Message.IndexOf([char]0) -ge 0 -or $Thread.IndexOf([char]0) -ge 0) { throw 'NUL characters cannot be passed to the native CLI.' } | ||
| $codexHomeDir = if ([string]::IsNullOrEmpty($env:CODEX_HOME)) { Join-Path $HOME '.codex' } else { $env:CODEX_HOME } | ||
| if ($Latest) { | ||
| $Thread = Resolve-LatestThread $codexHomeDir | ||
| Write-Warning '-Latest may select a different project or a subagent, not the foreground chat.' | ||
| } | ||
| $exe = Resolve-CodexExe $CodexExe $codexHomeDir | ||
| if ($DryRun) { | ||
| Write-Output "Codex: $exe" "Thread: $Thread" 'Dry run only; no message was queued.' | ||
| exit 0 | ||
| } | ||
| # Preserve the CLI's exit code. Never retry an ambiguous queue result automatically. | ||
| $code = Invoke-CodexNative $exe @('queue', "--thread=$Thread", "--message=$Message") | ||
| exit $code | ||
| } catch { | ||
| [Console]::Error.WriteLine($_.Exception.Message) | ||
| exit 1 | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.