Skip to content

feat(settings): add auto-detect button for Codex CLI path#47

Merged
Cmochance merged 4 commits into
mainfrom
feat/settings-codex-autodetect
May 29, 2026
Merged

feat(settings): add auto-detect button for Codex CLI path#47
Cmochance merged 4 commits into
mainfrom
feat/settings-codex-autodetect

Conversation

@Cmochance
Copy link
Copy Markdown
Owner

@Cmochance Cmochance commented May 29, 2026

What

Adds an Auto-detect button to Settings → "Codex CLI path", beside "Change".

The app needs the real codex binary to log in / launch Codex. Auto-discovery sometimes lands on a wrong or stale path, and some users don't know how to set it by hand — the existing dialog only offers manual entry + static "common location" hints.

The new button force-rescans (ignoring the cached/override path) every common install location + PATH, and verifies each candidate is actually runnable via codex --version (timeout-guarded, off the UI thread on the blocking pool):

  • 1 runnable hit → applied immediately, toast confirms.
  • several → opens the dialog with the verified candidates as click-to-fill chips.
  • none → falls back to the manual dialog, and distinguishes "no codex installed" from "codex present on disk but won't run" (broken install).

How

Backend (src-tauri)

  • New Tauri command redetect_codex_cli_path (async / spawn_blocking, since each probe spawns a child).
  • New CodexPathResolver::redetect_runnable_paths trait method; shared wait_child_with_timeout (poll-wait + kill, reaps on every branch so no zombies).
  • mac & Windows symmetric probe_codex_runnable (codex --version, per-platform timeout: 3s mac / 5s win) + redetect_runnable_codex_cli_paths (candidate-capped).
  • New CodexCliRedetectResult model.

Frontend (src-tauri/shared/front/*, one edit serves both mac & win): button wiring, handleDetectCodexCli, en/zh strings; HTML rows (mac+win) + CSS.

Local review hardening

Ran 3 local review agents (code-reviewer / silent-failure-hunter / pr-test-analyzer) before pushing and applied their findings:

  • Diagnostic logging on probe/wait failure (no more silently-swallowed io::Error in the timeout helper).
  • Lone-hit setCodexCliPath rejection → fallback to the dialog (prefilled) instead of a raw error toast.
  • Candidate cap (MAX_PROBE_CANDIDATES = 12) so a pathological PATH can't stall the scan.
  • "broken install" vs "not found" messaging in the zero-candidate case.

Tests / verification

  • cargo test --lib: 103 passed (incl. new wait_child_with_timeout fast-exit / overrun + redetect empty / multiple).
  • probe_codex_runnable test placed in the win module so it runs on the Linux CI cargo test --lib job (the mac module isn't test-run in CI).
  • tsc --noEmit: clean · vite build: ok · clippy: new code clean.
  • Windows branch compiles on Linux (verified statically; local x86_64-pc-windows-msvc cross-check is blocked by ring's C-SDK requirement — an env limitation, not a code issue — so CI's Linux job is the real gate for the win branch).

🤖 Generated with Claude Code


Open in Devin Review

The app must locate the real `codex` binary; auto-discovery sometimes
lands on a wrong/stale path and some users don't know how to set it by
hand. Add an "Auto-detect" button to Settings -> Codex CLI path that
force-rescans (ignoring the cached/override path) every common location
plus PATH and verifies each candidate is actually runnable via
`codex --version` (timeout-guarded, off the UI thread). A lone runnable
hit is applied immediately; several open the picker dialog with the
verified candidates; none falls back to manual entry -- and the empty
case distinguishes "no codex installed" from "codex present but won't run".

Backend: new `redetect_codex_cli_path` command (async/spawn_blocking) +
`CodexPathResolver::redetect_runnable_paths` + shared
`wait_child_with_timeout`; mac/win symmetric `probe_codex_runnable` +
`redetect_runnable_codex_cli_paths`. Frontend (shared, serves mac+win):
button, handler, en/zh strings. README (x2) + CHANGELOG updated.

Hardening from local review: diagnostic logs on probe/wait failure,
lone-hit set-rejection fallback to the dialog, candidate cap, and tests
for wait_child_with_timeout (shared) + probe_codex_runnable (win, runs on
the Linux CI job).
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Reworks the auto-detect button per hands-on testing feedback (the button
is the small-user last-resort, so it must find codex broadly and, when
there are several, list the real ones to pick from):

- More comprehensive discovery: add login-shell resolution
  (`$SHELL -lc 'command -v codex'`) so installs on the user's PATH
  (nvm / asdf / brew / fnm) are found even under Finder's narrow launchd
  PATH, while bypassing any `codex` shell function. Joined with the
  managed-shim resolver, Codex.app bundle, fixed locations, and PATH walk.
- Confirm authenticity + capture version: each candidate is probed with
  `codex --version` via a new shared `probe_version_with_timeout`
  (poll + kill + stdout capture), replacing the bool-only
  `wait_child_with_timeout`.
- Multiple installs -> list to pick: the dialog shows each verified
  candidate as `path | version` under a "detected (verified runnable)"
  heading with a "pick one" copy -- not the misleading "couldn't find
  codex" copy that previously appeared even when two were found.
- One install -> applied directly (one-click fallback); none -> manual
  dialog, distinguishing "broken install" from "not installed".

CodexCliCandidate{path, version} replaces bare path strings end to end
(model, trait, mac/win impls, TS types, dialog rendering). Tests updated:
probe_version_with_timeout (capture / non-zero / overrun), redetect
multi/empty, win probe_codex_version.
chatgpt-codex-connector[bot]

This comment was marked as resolved.

Local silent-failure review of the auto-detect rework flagged a CRITICAL:

- discover_codex_via_login_shell ran an UNBOUNDED `$SHELL -lc` — a slow or
  hung login profile (nvm / asdf / brew shellenv / network-y rc files)
  would wedge the whole scan: button stuck on "Detecting...", a leaked
  blocking-pool thread, and no diagnostic. It runs first and synchronously,
  so one wedged profile froze the whole feature. It now goes through a
  shared, timeout-bounded helper (8s), matching the per-candidate probes'
  own stated design intent.
- Extracted `run_capturing_stdout_with_timeout` (reader thread + poll +
  kill) out of `probe_version_with_timeout` so a child that writes more
  than the ~64KB pipe buffer before exiting can't backpressure its own
  pipe and deadlock until the timeout (MEDIUM). The version probe is now a
  thin wrapper taking the first stdout line; the login-shell resolve
  reuses the helper and takes the LAST line (skipping any profile banner).
- spawn errors are now logged instead of being dropped by `.ok()?` (LOW).

Adds a regression test that >64KB of stdout is drained, not deadlocked.
@Cmochance
Copy link
Copy Markdown
Owner Author

更新(commit 380e98d):

按反馈把「自动检测」做成给小白的全面兜底:

  • 更全面:加 login shell 解析($SHELL -lc 'command -v codex'),覆盖 nvm/asdf/brew/任何 PATH 配置,且解决从 Finder 启动的 .app 窄 PATH 找不到的问题;并入 managed-shim resolver + Codex.app bundle + 固定位置 + PATH。
  • 确认真实性 + 版本:每个候选跑 codex --version 验证可运行并捕获版本号。
  • 多个 → 列出让选:弹"检测到多个(已验证可运行)"对话框,每条显示 路径 · 版本;单个 → 直接应用(一键);0 个 → 才提示没找到/手动

复审发现并已修一个 CRITICAL:login shell 之前用无超时的 .output(),慢/卡的 profile 会 wedge 整个扫描、卡死按钮——现在统一走带超时(8s)+ reader 线程 drain 的共享 helper。

本地全绿:cargo test 104 passed、tsc clean、clippy 无新 warning。新 .app 已重新打包。

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Addresses 3 chatgpt-codex-connector P2 threads on the auto-detect PR:

- run_capturing_stdout_with_timeout: the stdout reader was join()'d
  unbounded after the child exited. If the child exits but leaves a
  background process holding the stdout pipe, read_to_string never sees
  EOF and join() blocks forever, defeating the timeout. The reader now
  posts to a channel and we wait with a bounded recv_timeout, abandoning
  the detached reader after a short grace.
- discover_real_codex_cli_from_shell: the managed-shim resolver script
  ran via unbounded `.output()`; it now goes through the same bounded
  helper as the login-shell resolve and the per-candidate probes, so no
  shell-touching spawn in the scan is unbounded anymore.

(The login-shell `$SHELL -lc` thread was already bounded in 380e98d.)
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +757 to +763
// Detected-mode opens after auto-detect found several runnable codex,
// so use a "pick one" copy — NOT the default "couldn't find it" copy
// that misled users into thinking detection had failed.
const hasDetected = detectedCandidates !== undefined && detectedCandidates.length > 0;
elements.codexCliDialogCopy.textContent = hasDetected
? t(state.locale, "codexCliDetectPickCopy")
: t(state.locale, "codexCliDialogCopy");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Periodic applyLocale() overwrites auto-detect dialog copy within 15 seconds of opening

When the auto-detect feature opens the dialog in "detected mode" (multiple candidates found), it sets custom dialog text at src-tauri/shared/front/actions.ts:761-763 ("pick one" copy) and a custom heading via renderCodexCliStatus at src-tauri/shared/front/actions.ts:699-701 ("Detected (verified runnable)"). However, rerenderDashboard() runs every 15 seconds (src-tauri/shared/front/actions.ts:1060-1062) and unconditionally calls applyLocale(), which resets elements.codexCliDialogCopy.textContent to the default "couldn't find codex" message (src-tauri/shared/front/render.ts:752) and resets elements.codexCliSuggestionsHeading to "Common locations" (src-tauri/shared/front/render.ts:756). The user sees the dialog text change from the correct "pick one" message to the misleading "couldn't find codex" message within at most 15 seconds of opening the dialog, despite the suggestion chips (the actual path buttons) remaining correct.

Prompt for agents
The bug is caused by the periodic `applyLocale()` (called from `rerenderDashboard()` every 15 seconds) unconditionally overwriting dialog text elements, even when those elements were set to contextual values by `openCodexCliDialog`.

Files involved:
- `src-tauri/shared/front/render.ts` lines 751-756 (`applyLocale` function): unconditionally sets `codexCliDialogCopy` and `codexCliSuggestionsHeading` to default i18n keys
- `src-tauri/shared/front/actions.ts` lines 757-763 and 698-701 (`openCodexCliDialog` and `renderCodexCliStatus`): set contextual values for detected mode

Possible approaches:
1. Have `applyLocale()` skip overwriting dialog copy/heading if the codex CLI dialog is currently open (check `elements.codexCliDialog.open`).
2. Store a flag (e.g. `state.codexCliDialogDetectedMode`) that `applyLocale()` checks before overwriting; clear it when the dialog closes.
3. Move the dialog text overwrite out of `applyLocale()` and into the dialog open/close logic exclusively.

Note: This same class of pre-existing issue also affects `submitCodexCliButton` text ("Save & retry login" reverts to "Save"), but it's most visible/confusing with the new "detected pick" vs "couldn't find" copy distinction.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@Cmochance Cmochance merged commit ec0e9a0 into main May 29, 2026
4 checks passed
@Cmochance Cmochance deleted the feat/settings-codex-autodetect branch May 29, 2026 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant