Skip to content

fix(environment): improve bash shell detection using shutil.which#1134

Open
yumesha wants to merge 2 commits intoMoonshotAI:mainfrom
yumesha:fix/bash-shell-detection
Open

fix(environment): improve bash shell detection using shutil.which#1134
yumesha wants to merge 2 commits intoMoonshotAI:mainfrom
yumesha:fix/bash-shell-detection

Conversation

@yumesha
Copy link

@yumesha yumesha commented Feb 13, 2026

Related Issue

N/A - This is a compatibility fix for systems with non-standard bash locations.

Description

This PR improves bash shell detection by using shutil.which() as the primary method, falling back to hardcoded paths only when necessary.

Changes:

  1. src/kimi_cli/utils/environment.py

    • Use shutil.which("bash") as primary detection method
    • Fall back to /bin/bash, /usr/bin/bash, /usr/local/bin/bash hardcoded paths only when which fails
    • Improves compatibility on systems where bash is not at standard locations
  2. tests/conftest.py

    • Update test environment to use shutil.which("bash") for shell path
  3. tests/core/test_default_agent.py

    • Add _normalize_bash_path() helper to normalize bash paths in tool descriptions
    • Enables cross-platform snapshot testing
  4. tests/tools/test_tool_descriptions.py

    • Normalize bash paths in Shell tool description tests
  5. tests/utils/test_pyinstaller_utils.py

    • Remove ripgrep binary from expected datas (no longer bundled)
  6. tests/utils/test_utils_environment.py

    • Add test coverage for new shell detection logic

Why:

On some systems or container environments, bash may not be located at /bin/bash. The previous hardcoded path approach would fail on these systems. Using shutil.which() allows the shell to be found regardless of its location in $PATH.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue, if any. (No issue - compatibility improvement)
  • I have added tests that prove my fix is effective or that my feature works.
  • I have run make gen-changelog to update the changelog. (Not needed for this fix)
  • I have run make gen-docs to update the user documentation. (Not needed for this fix)

Open with Devin

Use shutil.which() as the primary method for detecting bash location,
falling back to hardcoded paths only when necessary. This enables
proper shell detection on systems where bash is not located at
standard paths like /bin/bash.

Also updates tests to normalize bash paths for cross-platform
compatibility.
Copy link
Contributor

@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

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97c00b97c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +39 to +42
bash_in_path = shutil.which("bash")
if bash_in_path:
shell_name = "bash"
shell_path = KaosPath(bash_in_path)

Choose a reason for hiding this comment

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

P2 Badge Normalize which() result to an absolute shell path

shutil.which("bash") may return a relative path when PATH contains relative entries (for example, bin/bash), and this value is stored directly as Environment.shell_path. Because the runtime later switches cwd to session.work_dir before executing shell commands, Shell can end up trying to exec a path that no longer resolves, causing Shell tool calls to fail when startup cwd and session cwd differ. Converting bash_in_path to an absolute path before storing it avoids this regression.

Useful? React with 👍 / 👎.

When using kimi -c "prompt" or kimi --command "prompt", the CLI now
automatically runs in print mode (non-interactive) similar to python -c
or claude -c behavior. This makes the -c flag work as expected for
single-shot command execution without requiring --print flag.
Copy link
Contributor

@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 2 new potential issues.

View 8 additional findings in Devin Review.

Open in Devin Review

Comment on lines +391 to 406
# Track if -c/--command was explicitly used (vs --prompt/-p)
# to auto-enable print mode for command-style usage
command_mode = False
for arg in sys.argv[1:]:
if arg in ("-c", "--command"):
command_mode = True
break

if prompt is not None:
prompt = prompt.strip()
if not prompt:
raise typer.BadParameter("Prompt cannot be empty", param_hint="--prompt")

ui: UIMode = "shell"
if print_mode:
if print_mode or command_mode:
ui = "print"
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 command_mode bypasses UI conflict detection, silently overriding --acp/--wire and enabling yolo

When a user runs kimi -c "do something" --acp or kimi -c "do something" --wire, the command_mode flag silently overrides the explicitly requested UI mode and implicitly enables yolo (auto-approve all actions).

Root Cause

The command_mode variable is determined by raw sys.argv scanning at lines 393-397, but it is not included in the conflict_option_sets check at src/kimi_cli/cli/__init__.py:357-382. That check only examines print_mode:

conflict_option_sets = [
    {
        "--print": print_mode,   # only checks print_mode, not command_mode
        "--acp": acp_mode,
        "--wire": wire_mode,
    },
    ...
]

So when the user passes -c with --acp, the conflict check sees --print: False and --acp: True — only one active option — and passes. Then at line 405, command_mode takes priority:

if print_mode or command_mode:  # command_mode is True
    ui = "print"               # overrides --acp / --wire
elif acp_mode:
    ui = "acp"                 # never reached

Further, at line 497, ui == "print" implicitly enables yolo: yolo=yolo or (ui == "print"), meaning all tool actions are auto-approved without user consent.

Impact: The user's explicitly requested --acp or --wire mode is silently ignored, and dangerous auto-approval is enabled without the user's knowledge.

Prompt for agents
The command_mode flag must be integrated into the existing conflict detection system. In src/kimi_cli/cli/__init__.py, either:

1. Move the command_mode detection (lines 391-397) to BEFORE the conflict_option_sets block (line 357), and include command_mode in the conflict set alongside print_mode. For example, change the first conflict set to:
   {"--print": print_mode, "--command": command_mode, "--acp": acp_mode, "--wire": wire_mode}
   This ensures that -c/--command conflicts with --acp and --wire are detected and raise a clear error.

2. Alternatively, merge command_mode into print_mode before the conflict check:
   if command_mode:
       print_mode = True
   Then the existing conflict detection handles it naturally.
Open in Devin Review

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

Comment on lines +393 to +397
command_mode = False
for arg in sys.argv[1:]:
if arg in ("-c", "--command"):
command_mode = True
break
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Raw sys.argv scan for -c can match option values, causing false command_mode activation

The sys.argv scan at lines 394-397 iterates over all argv entries without understanding option-value pairing, so -c appearing as a value to another option (e.g., kimi --session -c) is incorrectly detected as command_mode = True.

Detailed Explanation

The code scans sys.argv[1:] linearly:

for arg in sys.argv[1:]:
    if arg in ("-c", "--command"):
        command_mode = True
        break

Consider kimi --session -c -p "hello". Typer parses --session as expecting a string value, so it consumes -c as the session ID (i.e., session_id = "-c"). The prompt parameter gets its value from -p "hello". However, the raw sys.argv scan finds -c at index 2 and sets command_mode = True, causing ui = "print" and implicitly enabling yolo at src/kimi_cli/cli/__init__.py:497. The user intended shell mode with session "-c" but instead gets print mode with auto-approval.

Impact: Unexpected UI mode and implicit yolo activation when -c happens to appear as a value to another option.

Open in Devin Review

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

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