fix(run-mode): gate dropdown entries on CLI availability - #201
Conversation
Follow-up to the welcome-screen gating (Ark0N#200): the run-mode dropdown (gear menu next to Run) had the same problem — Claude/Opencode/Codex/ Gemini entries were always shown regardless of whether the CLI is actually installed, so picking one could spawn a session that immediately errors out. - Add _refreshRunModeAvailability() (session-ui.js), called each time the dropdown opens; hides entries whose /api/<cli>/status reports unavailable. - Shell is intentionally never gated (no external CLI dependency). Depends on isClaudeAvailable()/GET /api/claude/status, which don't exist on upstream/master yet — duplicated here from Ark0N#200 so this PR is self-contained and independently mergeable. Once Ark0N#200 lands this branch should be rebased onto master, which will collapse the duplicate cleanly.
d4a24b5 to
660b320Compare…over antigravity Follow-up to #200 and #201, which gate the welcome buttons and the run-mode dropdown on whether the CLI is actually installed. Four corrections: 1. #200 also DELETED the Cloudflare Tunnel welcome button and the QR widget outright. Its rationale is right (offering a tunnel where cloudflared is not installed is a bad default) but the conclusion overshoots: the welcome QR is the whole scan-to-connect-from-your-phone flow, and deleting it left a large block of live tunnel code in settings-ui.js driving elements that no longer existed. Both are restored and the button is gated on cloudflared, which is what the stated rationale actually asks for. New cloudflared-resolver.ts mirrors the CLI resolvers, and TunnelManager now shares its search path so the button and the spawn can never disagree about where cloudflared lives. 2. Antigravity was missing from the run-mode gating, the one run mode LEAST likely to be installed. It slipped past because #201 predates it. Covered now, plus a static test that fails if a sixth mode reaches the dropdown without being gated, so the next one cannot slip the same way. 3. The per-surface fetches are replaced by the injected availability object already used for the Codex settings tab, so the codebase has one mechanism rather than two. The status routes buy nothing as a gating source: every resolver memoizes its PATH probe server-side, so a fetch is exactly as stale as an injected value while costing a round trip every time the dropdown opens and leaving the welcome buttons to flicker in after paint. The routes themselves stay, including the /api/claude/status that #200 adds. 4. Unknown availability now reads as AVAILABLE for run buttons. Both PRs hid the button on a failed fetch, so a blip left a working install with nothing to click; a genuinely missing CLI only ever produced an error toast. The Codex settings TAB keeps the opposite default, since hiding it costs nothing. The dropdown query is also scoped to the menu: `.run-mode-option` is the class the saved-dashboard and history rows use too, and a document-wide querySelector would have found whichever came first in the DOM. Fixes a latent environment-sensitivity in 816d900 while here: the index-title test asserted the template was untouched apart from the title, which held only on a machine with no codex installed. Verified end-to-end against a real server on an isolated instance+socket, with Playwright: gemini/codex hidden and claude/opencode/antigravity/shell shown, matching this host, tunnel button back, Codex settings tab still hidden, no console errors. Full test:ci sweep green (3902 tests). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ark0N
commented
Aug 4, 2026
Merged, thank you! And thanks for splitting it out of #200 with the duplication called out explicitly, git deduped the shared hunk on merge exactly as you predicted, so this cost nothing to land. Follow-up in Antigravity was missing. It is a run mode like the rest and The query is scoped to Availability comes from the injected object rather than four fetches per open, same change as on #200: the server-side resolvers memoize, so a fetch is no fresher than an injected value while costing four round trips every single time the gear menu opens. Also failing open on unknown now, since the Keeping Shell ungated was the right call, incidentally, it is what guarantees the menu is never empty on a box with nothing installed. Kept and pinned in a test. Thanks again! |
MOBILE_OVERVIEW_RUN_MODES / _buildMobileOverviewRunMenu is a separate, hardcoded duplicate of the toolbar's #runModeMenu (mobile-overview.js is a newer feature that mirrors the toolbar menu's look/behavior rather than reusing its render), so it never picked up Ark0N#201's isCliAvailable() gating and offered every backend regardless of what the server actually has installed. Gate it the same way: skip an entry unless isCliAvailable(mode), shell always exempt. Added functional + static regression tests mirroring the toolbar menu's own test pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MOBILE_OVERVIEW_RUN_MODES / _buildMobileOverviewRunMenu is a separate, hardcoded duplicate of the toolbar's #runModeMenu (mobile-overview.js is a newer feature that mirrors the toolbar menu's look/behavior rather than reusing its render), so it never picked up #201's isCliAvailable() gating and offered every backend regardless of what the server actually has installed. Gate it the same way: skip an entry unless isCliAvailable(mode), shell always exempt. Added functional + static regression tests mirroring the toolbar menu's own test pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The run-mode dropdown hides CLIs that are not installed on the HOST (Ark0N#201). That is right for local sessions and wrong for a container case, whose agents run inside the container: a host with no claude installed hides the mode while the container ships one, which is exactly what happened on a real deployment. The adoption preflight already probes what the container has, so that result is persisted on the case and surfaced through CaseInfo. Docker cases gate on it; every other case keeps the host probe unchanged. An absent list reads as "do not gate" rather than "nothing available": an owned container runs our base image, which ships every CLI, and treating unknown as empty would leave the menu with Shell alone.
Summary
Follow-up to #200 (welcome-screen buttons). The run-mode dropdown (gear icon next to Run) has the same problem: Claude/Opencode/Codex/Gemini entries are always shown regardless of whether the CLI is actually installed, so picking one can spawn a session that immediately errors out.
_refreshRunModeAvailability()insession-ui.js, called each time the dropdown opens; hides an entry whose/api/<cli>/statusreportsavailable: false.Depends on #200:
isClaudeAvailable()/GET /api/claude/statusdon't exist onmasteryet, so this branch duplicates that small addition from #200 to stay self-contained and independently reviewable/mergeable. Once #200 lands, this branch should be rebased ontomaster, which will cleanly collapse the duplicate (trivial conflict, or none if git dedupes it).Test plan
tsc --noEmitcleaneslintclean on touched.tsfilescheck:frontend-syntaxandcheck:public-assetspass/api/*/statusendpoints via curl against a throwaway dev server