Tightens the experimental OpenAI Codex (ChatGPT) provider so the v0.8.55
gate is green.
- clippy: collapse 5 nested if/if-let blocks flagged by
clippy::collapsible_if into let-chains (oauth.rs env-override
resolution, responses.rs SSE delta handling). cargo clippy --workspace
--all-targets -- -D warnings is now clean.
- fmt: cargo fmt --all over the Codex/Together changes (the gate's
--check was failing, incl. a mangled "| ApiProvider::Ollama").
- default model: Config::default_model() now resolves to the Codex
default (gpt-5.5) for the Codex provider instead of leaking a DeepSeek
default_text_model the Responses backend rejects. The carve-out sits
after the explicit provider-scoped model block (so
[providers.openai_codex] model still wins) and before the
DeepSeek-validating path, which is unchanged. Adds a behavior test.
https://claude.ai/code/session_013cHWv5sR6XPnVWzfMP8uma
Verified live against the ChatGPT Codex backend (real codex login):
`exec --model gpt-5.5` through the openai-codex provider returns a correct
completion. Fixes found while getting there:
- Route the non-streaming path too. create_message only dispatched chat
completions; for OpenAI Codex it now drives the Responses stream and folds it
into a MessageResponse (handle_responses_message), so `exec` and other
non-streaming callers use the same wire path as the interactive stream.
- Present a non-browser User-Agent on the Codex path. The ChatGPT backend sits
behind Cloudflare, which served a JS challenge (HTTP 403) to our browser-like
"Mozilla/5.0 (compatible; codewhale/...)" UA. A codex_cli_rs UA passes.
- Always send `instructions` (Responses rejects empty instructions); fall back
to a minimal system prompt.
- Map reasoning effort onto the Codex-allowed set (none/minimal/low/medium/
high/xhigh); CodeWhale's "auto" has no equivalent and maps to medium.
- Send `Accept: text/event-stream`.
Antislop pass on the changeset:
- Inline the one-caller codex_access_token wrapper (config calls get_credentials
directly) and drop the one-caller credentials_present helper; both presence
checks now use auth_file_path().exists() consistently with the Kimi path.
- Remove dead stream-parser state (ToolCallState fields, unused response_id /
current_item_type / output_text / thinking_text accumulators).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the in-progress OpenAI Codex provider and bumps the workspace to
0.8.55. Builds on the committed Together AI provider + model catalog work.
OpenAI Codex (ChatGPT) provider — experimental:
- Wire the previously-dead OAuth module into credential resolution. The TUI
config now resolves the access token via the Codex CLI login in
~/.codex/auth.json (env overrides OPENAI_CODEX_ACCESS_TOKEN/CODEX_ACCESS_TOKEN),
refreshing expired tokens synchronously via the OpenAI token endpoint —
mirroring the existing Kimi OAuth flow rather than introducing a new pattern.
- Send the ChatGPT backend's required headers from the Responses client
(chatgpt-account-id, OpenAI-Beta: responses=experimental, originator) and
stop duplicating the Authorization header already installed on the client.
- Fix the cli crate's non-exhaustive ProviderKind matches (compile blocker).
Consistency / de-slop pass (so the provider fits the whole app, not one path):
- has_api_key_for / active_provider_has_config_api_key now detect the Codex
OAuth login on disk, the same way they detect Kimi OAuth — a `codex login`
user is no longer reported as unauthenticated.
- Replace the bogus OPENAI_CODEX_API_KEY hint (which exists nowhere else) with
the real OPENAI_CODEX_ACCESS_TOKEN/CODEX_ACCESS_TOKEN in the auth-error and
picker surfaces.
- Drop dead state in the Responses stream parser (unused ToolCallState fields /
imports); tool-call data is streamed live.
- Update docs/PROVIDERS.md, config.example.toml, and the provider-metadata wire
test for the Responses wire format.
Release:
- Bump workspace + crates + npm package to 0.8.55; update CHANGELOG.md and
crates/tui/CHANGELOG.md.
Note: the live Responses round-trip has not been exercised against the
production ChatGPT backend in this environment; the provider ships as preview.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`pdf_extract::extract_text` uses an internal codepath that can hang on
certain PDF cross-reference tables or font encodings. The per-page
`extract_text_by_pages` path does not trigger this hang and produces
identical output when joined.
When `pages` is not specified, route through `extract_text_by_pages`
and join all pages instead of calling `extract_text`.
Fixes#2641.
The 1ms heartbeat timeout raced the synchronous touch()->cleanup()
gap on loaded CI runners (Windows scheduler can deschedule >1ms),
intermittently reaping the just-touched agent so cleanup() returned 1.
Widen the timeout to 50ms and the staleness sleep to 150ms to keep the
logic exercised without the timing race. Addresses CI flakiness under
the v0.9.0 stabilization gate (#2721).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Harvested from PR #2885 by @greyfreedom. Wires ask-rules into the
app-server and core ExecPolicyEngine (previously inert). Removes the
original PR's NeedsApproval arm that incorrectly allow-listed the
working directory as a network host.
Co-Authored-By: greyfreedom <11493871+greyfreedom@users.noreply.github.com>
Harvests 7 safe fixes from PR #2880 by @HUQIANTAO: tool-name hex-digit
guard, token-usage u32 clamp, read-file line usize::try_from, grep
context-lines cap, UTF-8 PDF trim, run_skill dedup, and
Volcengine/SiliconflowCn reasoning_content support. Excludes the
DeepSeek stream-stop change and the unwired prompt_persist module
(deferred for separate review).
Co-Authored-By: HUQIANTAO <58421104+HUQIANTAO@users.noreply.github.com>
Mechanical rustfmt of the runtime_prompt tests rewritten in PR #2874
(LeoAlex0). No logic change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tokio::task::spawn_blocking requires a running tokio runtime, which
breaks tests that call hook functions outside a tokio context. Since
hooks are fire-and-forget (no JoinHandle needed), std::thread::spawn
is the correct choice.
Replace platform-specific std::os::unix::io::FromRawFd with
Box<dyn std::io::Write + Send> return type. This compiles on
Windows, macOS, and Linux without unsafe code.
The closure now returns a boxed writer that is either:
- The cloned file handle (success case)
- A reopened file handle (clone failed)
- stderr (last resort, prevents panic)
1. Replace let-else with if-let-Some to avoid compilation error
- let-else with return would return from the entire function
- if-let-Some correctly assigns to tool_registry and continues
2. Preserve original goal_objective_for_prompt behavior
- Return None (not fallback) when objective exists but goal is inactive
- Use state.is_active().then() to match original semantics
The content_index is only incremented AFTER a block is closed, not
when opened. Using saturating_sub(1) would close the wrong block.
The reviewer correctly identified this logic error.
1. Fix Mutex lock().unwrap() in MCP server (mcp_server.rs:384,434)
- Use unwrap_or_else(|e| e.into_inner()) to recover from poisoned locks
- Previously, a single panic while holding the lock would cascade to all threads
2. Fix std::thread::spawn in async code (hooks.rs:1055)
- Replace std::thread::spawn with tokio::task::spawn_blocking
- Respects tokio's thread pool limits instead of creating unbounded OS threads
- Fire-and-forget hook execution now properly managed by tokio runtime
3. Fix dropped JoinHandle in SSE loop (mcp.rs:647)
- Store the JoinHandle in SseTransport struct
- Enables detection of SSE loop termination
- Prevents silent connection loss without structured error reporting
4. Fix std::sync::Mutex poison handling in cost_status (cost_status.rs:28-58)
- Use unwrap_or_else(|e| e.into_inner()) to recover from poisoned locks
- Previously, a panic while holding the lock silently lost all subsequent cost data
- Cost tracking now survives mutex poisoning
5. Fix .expect() in tracing writer (runtime_log.rs:162)
- Replace expect() with fallback chain: try_clone -> reopen file -> stderr
- Prevents panicking inside tracing subscriber on fd exhaustion
- Previously, EMFILE during logging would crash the application
1. Fix deny rule prefix matching without word boundary (execpolicy/lib.rs:351-353)
- Deny rule 'rm' now blocks 'rm -rf /' but NOT 'rmdir' or 'rmview'
- Previously used bare starts_with which matched any command starting with 'rm'
- Add word-boundary check: command must equal rule or start with rule+space
2. Fix fallback prefix match clarity (execpolicy/bash_arity.rs:362-374)
- Improve comment to clarify word-boundary matching behavior
- The trailing space in starts_with already provides word boundary
3. Fix hardcoded AskForApproval::OnRequest in HTTP API (app-server/lib.rs:283)
- Read approval_policy from config instead of hardcoding OnRequest
- Users with 'auto'/'yolo' policy now get UnlessTrusted for API calls
- Previously ignored user's configured security posture
4. Fix fuzzy indentation search destroying preceding text (tools/file.rs:714-735)
- When match starts mid-line after whitespace stripping, use exact position
- Previously always expanded to line start, destroying preceding content
- Now only expands to line start when match is at a line boundary
5. Fix potential underflow in apply_hunk start index (tools/apply_patch.rs:1110-1115)
- Use checked_add_signed to safely handle negative cumulative_offset
- Prevents isize overflow on adversarial patch input
- Clamp to lines.len() instead of relying on .max(0) cast
1. Fix swallowed persist_config errors (app-server/lib.rs:882,896)
- Log errors when config persistence fails after set/unset
- Users previously got success response even when disk write failed
2. Fix swallowed job store load error (core/lib.rs:751)
- Add warning log when job store fails to load at startup
- Previously silently started with empty job list on corruption
3. Fix silent config parse failures (config/lib.rs:1590)
- Log warning when project config TOML is malformed
- Previously returned None indistinguishable from 'no config file'
4. Fix MCP connect_all errors swallowed (mcp.rs:2151,2189)
- Log warnings for each server that fails to connect
- Previously returned incomplete resource list with no indication
5. Fix error context stripped in engine status (core/engine.rs:2223)
- Use {err:#} format to include full error chain
- Was inconsistent with line 2234 which already used {err:#}
6. Fix tool audit log failures silently dropped (tool_execution.rs:122-136)
- Log each failure: serialization, directory creation, file open, write
- Previously silently dropped all errors for security audit trail
7. Fix Err(_) arms discarding error info (runtime_log.rs:179, runtime_threads.rs:828)
- Log stderr redirect failures on Windows
- Log poisoned mutex in pending_approvals
8. Fix env var parsing errors silently ignored (config/lib.rs:2519-2530)
- Warn when DEEPSEEK_TELEMETRY, DEEPSEEK_YOLO, DEEPSEEK_HTTP_HEADERS
have invalid values instead of silently treating as unset
9. Fix MCP config reload errors swallowed (mcp.rs:2011)
- Log config reload errors instead of complete silence
10. Fix .expect() on sub-agent runtime (core/engine.rs:1715)
- Gracefully fall back to basic tool set when API client missing
- Previously panicked if subagents enabled but no client configured
11. Fix .expect() on goal objective (core/engine.rs:2543)
- Use safe if-let pattern instead of check+expect
- Prevents panic if refactoring changes control flow