Skip to content

fix: show clear error when only one CLI tool is installed#3

Merged
yigitkonur merged 1 commit intoyigitkonur:mainfrom
barisgirismen:fix/single-tool-error-message
Feb 20, 2026
Merged

fix: show clear error when only one CLI tool is installed#3
yigitkonur merged 1 commit intoyigitkonur:mainfrom
barisgirismen:fix/single-tool-error-message

Conversation

@barisgirismen
Copy link
Copy Markdown
Contributor

@barisgirismen barisgirismen commented Feb 19, 2026

When only one CLI tool is installed, clack.select() receives an empty options array and crashes with:
Crash

This adds a guard in both places where target tool selection happens. Instead of crashing, it now tells the user which tool was detected and lists the ones they could install:
CleanShot 2026-02-20 at 02 26 48

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of cross-tool handoff scenarios when no alternative tools are available, with clearer warning messages informing users about missing tools and session constraints.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Adds defensive early-return guards in interactive tool handoff flows within the CLI. When no alternative tools are available for cross-tool handoff, the code now gathers all tools, identifies missing ones, logs a warning to the user, and exits early before attempting tool selection.

Changes

Cohort / File(s) Summary
Interactive Tool Handoff Guards
src/cli.ts
Added two early-return guards in interactive flows that trigger when targetOptions is empty. Each guard collects all available tools, computes the difference against installed/available tools, logs a warning message listing the source tool and missing alternatives, and returns without proceeding to selection UI.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🛡️ Two guards stand watch at the handoff gate,
When no tools remain to compensate,
A warning whispers what's not there,
Early returns with gentle care. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding error handling when only one CLI tool is installed, preventing crashes and showing clear user guidance.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/cli.ts (1)

297-302: Extract the duplicate guard into a helper; derive allTools from the type constant.

The same 5-line block appears verbatim in both code paths. Beyond DRY, the hardcoded allTools array (['claude', 'codex', 'copilot', 'gemini', 'opencode', 'droid']) can silently drift from the SessionSource union type if a new tool is ever added. There's also a subtle edge case: missing is computed as allTools.filter(t => !availableTools.includes(t)), which will include session.source itself when that tool is not in availableTools (e.g., session file outlives the uninstalled tool) — making the "Only X is installed" message factually wrong.

♻️ Proposed refactor

Define a single source-of-truth constant near the sourceColors map (or in src/types/index.ts), and extract a helper:

+// All supported tools — keep in sync with SessionSource type
+const ALL_TOOLS: SessionSource[] = ['claude', 'codex', 'copilot', 'gemini', 'opencode', 'droid'];

+function warnNoTargetTools(sessionSource: SessionSource, availableTools: SessionSource[]): void {
+  const missing = ALL_TOOLS
+    .filter(t => t !== sessionSource && !availableTools.includes(t))
+    .map(t => t.charAt(0).toUpperCase() + t.slice(1));
+  clack.log.warn(
+    `Only ${sourceColors[sessionSource](sessionSource)} is installed. ` +
+    `Install at least one more (${missing.join(', ')}) to enable cross-tool handoff.`
+  );
+  clack.outro(chalk.gray('Install more tools to enable handoff'));
+}

Then replace both guard blocks with:

-    if (targetOptions.length === 0) {
-      const allTools: SessionSource[] = ['claude', 'codex', 'copilot', 'gemini', 'opencode', 'droid'];
-      const missing = allTools.filter(t => !availableTools.includes(t)).map(t => t.charAt(0).toUpperCase() + t.slice(1));
-      clack.log.warn(`Only ${sourceColors[session.source](session.source)} is installed. Install at least one more (${missing.join(', ')}) to enable cross-tool handoff.`);
-      return;
-    }
+    if (targetOptions.length === 0) {
+      warnNoTargetTools(session.source, availableTools);
+      return;
+    }

The t !== sessionSource addition also fixes the edge case where session.source is not in availableTools (session from an uninstalled tool), preventing it from appearing in the install suggestions.

Also applies to: 516-521

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 297 - 302, Extract the duplicated guard into a
reusable helper and derive the list of known sources from a single
source-of-truth constant instead of hardcoding; e.g. add a constant
ALL_SESSION_SOURCES (next to sourceColors or in src/types) typed as
SessionSource[] and implement a helper function (e.g.
ensureCrossToolHandoff(targetOptions, availableTools, sessionSource)) that
returns early when targetOptions.length === 0, computes missing =
ALL_SESSION_SOURCES.filter(t => t !== sessionSource &&
!availableTools.includes(t)).map(...) and emits the same clack.log.warn using
sourceColors[sessionSource](sessionSource); then replace the two identical
5-line blocks (the one using targetOptions/availableTools/session.source) with a
call to this helper so the list stays in sync with the SessionSource type and
the edge case where session.source is absent is handled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli.ts`:
- Around line 297-302: The early-return guard in interactivePick/resume that
checks if targetOptions.length === 0 logs a warning with clack.log.warn(...) but
never closes the TUI; call clack.outro() immediately before the return to
properly close the session (do the same for the other similar guard that
currently returns after clack.log.warn, e.g., the occurrence around the second
availability check). Ensure you add clack.outro() right before each return in
those guard blocks so the TUI opened by clack.intro() is always closed.

---

Nitpick comments:
In `@src/cli.ts`:
- Around line 297-302: Extract the duplicated guard into a reusable helper and
derive the list of known sources from a single source-of-truth constant instead
of hardcoding; e.g. add a constant ALL_SESSION_SOURCES (next to sourceColors or
in src/types) typed as SessionSource[] and implement a helper function (e.g.
ensureCrossToolHandoff(targetOptions, availableTools, sessionSource)) that
returns early when targetOptions.length === 0, computes missing =
ALL_SESSION_SOURCES.filter(t => t !== sessionSource &&
!availableTools.includes(t)).map(...) and emits the same clack.log.warn using
sourceColors[sessionSource](sessionSource); then replace the two identical
5-line blocks (the one using targetOptions/availableTools/session.source) with a
call to this helper so the list stays in sync with the SessionSource type and
the edge case where session.source is absent is handled.

Comment on lines +297 to +302
if (targetOptions.length === 0) {
const allTools: SessionSource[] = ['claude', 'codex', 'copilot', 'gemini', 'opencode', 'droid'];
const missing = allTools.filter(t => !availableTools.includes(t)).map(t => t.charAt(0).toUpperCase() + t.slice(1));
clack.log.warn(`Only ${sourceColors[session.source](session.source)} is installed. Install at least one more (${missing.join(', ')}) to enable cross-tool handoff.`);
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add clack.outro() before each early return to close the TUI session properly.

Both guard blocks return after clack.log.warn() but neither calls clack.outro(). Since clack.intro() was already called (line 178 in interactivePick, line 502 in the resume interactive branch), the terminal will show an unclosed intro top-bar — every other exit path in these functions calls outro() or cancel().

🛠️ Proposed fix (same pattern for both occurrences)
  if (targetOptions.length === 0) {
    const allTools: SessionSource[] = ['claude', 'codex', 'copilot', 'gemini', 'opencode', 'droid'];
    const missing = allTools.filter(t => !availableTools.includes(t)).map(t => t.charAt(0).toUpperCase() + t.slice(1));
    clack.log.warn(`Only ${sourceColors[session.source](session.source)} is installed. Install at least one more (${missing.join(', ')}) to enable cross-tool handoff.`);
+   clack.outro(chalk.gray('Install more tools to enable handoff'));
    return;
  }

Also applies to: 516-521

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 297 - 302, The early-return guard in
interactivePick/resume that checks if targetOptions.length === 0 logs a warning
with clack.log.warn(...) but never closes the TUI; call clack.outro()
immediately before the return to properly close the session (do the same for the
other similar guard that currently returns after clack.log.warn, e.g., the
occurrence around the second availability check). Ensure you add clack.outro()
right before each return in those guard blocks so the TUI opened by
clack.intro() is always closed.

@yigitkonur yigitkonur merged commit 3d6943d into yigitkonur:main Feb 20, 2026
1 check passed
@yigitkonur
Copy link
Copy Markdown
Owner

thanks @barisgirismen, finally we're collab on sth!

yigitkonur added a commit that referenced this pull request Feb 25, 2026
Rewrote the entire README to read like an actual developer wrote it:
- Punchier intro that leads with the problem, not a feature list
- Collapsed the 14×14 checkmark matrix into one sentence
- Cut 430→238 lines by removing redundancy and AI-doc patterns
- Added Community Contributions section referencing #1, #3, #4, #14
- Documented the 7 new agents (Amp, Kiro, Crush, Cline, Roo Code,
  Kilo Code, Antigravity) and bugs fixed in this round
- Prose over bullet spam, natural flow over exhaustive structure

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants