Skip to content

fix(optimizer): honor CLAUDE_SMART_CLI_PATH#127

Merged
yyiilluu merged 2 commits into
ReflexioAI:mainfrom
wenchanghan:codex/opencode-optimizer-cli-path
Jul 11, 2026
Merged

fix(optimizer): honor CLAUDE_SMART_CLI_PATH#127
yyiilluu merged 2 commits into
ReflexioAI:mainfrom
wenchanghan:codex/opencode-optimizer-cli-path

Conversation

@wenchanghan

@wenchanghan wenchanghan commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What's broken / annoying

When claude-smart runs under OpenCode, the backend already knows which local bridge should handle Claude-style AI calls:

  • OpenCode should go through opencode-claude-compat
  • Claude Code can keep using the normal claude CLI
  • Codex uses the optimizer assistant's native codex exec path, not the Claude-compat bridge

Most Reflexio calls already follow their host-specific path. But the optimizer assistant had one old path that still looked up claude directly on non-Codex hosts.

The first version of this PR fixed that lookup, but it sent full Claude Code safety flags to the OpenCode compatibility bridge. That bridge intentionally accepts only a small Claude-provider argument surface, so the optimizer would fail on OpenCode instead of routing correctly.

What's changing

  • The optimizer assistant still checks CLAUDE_SMART_CLI_PATH before falling back to the normal claude CLI.
  • When the resolved executable is a claude-smart compatibility bridge, the optimizer sends only the bridge-supported arguments.
  • When the resolved executable is the real Claude CLI, the optimizer keeps the existing guarded flags: plan mode, read-only tools, mutating tools disabled, no MCP, and no session persistence.
  • Codex behavior is kept explicit: Codex host runs through _run_codex() and ignores CLAUDE_SMART_CLI_PATH for optimizer rollouts.
  • Tests now exercise the real OpenCode bridge parser with a fake OpenCode binary, so unsupported optimizer-only Claude flags cannot silently return.
flowchart TD
    A[Optimizer needs an AI response] --> B{Which host is running?}

    B -->|Claude Code| C[Resolve normal claude CLI]
    C --> D[Use guarded Claude flags]
    D --> E[Same Claude Code behavior as before]

    B -->|OpenCode before fix| F[Resolve opencode-claude-compat]
    F --> G[Send unsupported Claude Code flags]
    G --> H[Bridge rejects the call]

    B -->|OpenCode after fix| I[Resolve opencode-claude-compat]
    I --> J[Send bridge-supported args only]
    J --> K[Request runs through OpenCode bridge]

    B -->|Codex| L[Use native codex exec optimizer path]
    L --> M[Same Codex behavior as before]

    classDef broken fill:#ffe5e5,stroke:#c62828,color:#111;
    classDef changed fill:#e8f5e9,stroke:#2e7d32,color:#111;
    classDef same fill:#eef3ff,stroke:#315fbd,color:#111;

    class F,G,H broken;
    class I,J,K changed;
    class C,D,E,L,M same;
Loading

How the product behaves afterwards

  • OpenCode installs route optimizer calls through the OpenCode bridge without rejected arguments.
  • Claude Code installs keep the same guarded Claude CLI behavior as before.
  • Codex installs keep the same native Codex optimizer behavior as before.
  • A bad explicit bridge path still fails loudly instead of silently falling back to claude, because silent fallback would recreate the quota/routing bug.

Validation

  • uvx ruff check plugin/src/claude_smart/optimizer_assistant.py plugin/src/claude_smart/env_config.py tests/test_optimizer_assistant.py -> passed
  • uv run --project plugin pytest tests/test_optimizer_assistant.py tests/test_opencode_support.py::test_opencode_backend_service_uses_bridge_without_opencode_path_probe tests/test_codex_support.py::test_codex_hooks_use_expected_events_and_marketplace_fallback -> 14 passed
  • npm ci -> installed locked Node dev dependencies needed by packaging/TypeScript checks
  • uv run --project plugin pytest -> 642 passed
  • Live OpenCode smoke: CLAUDE_SMART_HOST=opencode with real opencode-claude-compat and local opencode 1.17.12 returned {"content":"OK"}
  • Live Codex smoke: CLAUDE_SMART_HOST=codex with local codex-cli 0.142.5 returned {"content":"OK"}

Risks / follow-ups

  • This does not add executable-bit validation before spawning the bridge. Today that remains a loud subprocess failure, which is safer than silently falling back to the wrong CLI.
  • A future cleanup could centralize CLI path resolution across the optimizer assistant, install checks, and the vendored Reflexio Claude provider.

Why this is needed

This closes the gap where OpenCode optimizer rollouts could either bypass the host bridge or, after the initial PR version, fail because the bridge received unsupported Claude Code-only flags.

Why this is net-positive

Developers get predictable local routing and clearer failure behavior. OpenCode gets the bridge path it expects, Claude Code keeps its guarded CLI behavior, and Codex keeps its existing native optimizer path.

Summary by CodeRabbit

  • New Features

    • Added support for choosing a specific Claude CLI executable via an environment override, with automatic fallback to the installed command when not set.
    • Improved handling for compatibility bridges so they can route requests without extra rollout-specific restrictions.
  • Bug Fixes

    • Clarified local configuration guidance for how Claude Smart routes to host or cloud providers.
    • Improved error handling when the configured CLI path cannot be found.

When CLAUDE_SMART_HOST=opencode (or codex), backend-service.sh sets
CLAUDE_SMART_CLI_PATH to the host-specific compatibility bridge
(opencode-claude-compat or codex-claude-compat) so the reflexio
claude-code provider generates via the same bridge.

optimizer_assistant.py was previously hardcoded to spawn the real
"claude" CLI via shutil.which. With CLAUDE_SMART_HOST=opencode this
bypassed the bridge on the very next optimizer rollout and consumed CC
quota even after the user had switched reflexio's auto-detected model
to MiniMax-M3 via MINIMAX_API_KEY.

Extracted _resolve_claude_cli_path() that honours CLAUDE_SMART_CLI_PATH
and falls back to shutil.which("claude") when unset, so non-bridge
hosts (vanilla Claude Code, any environment that doesn't set the
override) continue to behave unchanged.

Also expand the CLAUDE_SMART_USE_LOCAL_CLI default comment so future
installs write a description that explains both the 1 and 0 semantics
(previously the wording implied it always routes through Claude Code
regardless of value, which made the .env confusing to scan).

Tests added: bridge override preferred, fallback when unset,
resolver handles empty override.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9080061f-aa04-427f-8f23-b7398ab778ea

📥 Commits

Reviewing files that changed from the base of the PR and between f272e13 and 43cdff6.

📒 Files selected for processing (2)
  • plugin/src/claude_smart/optimizer_assistant.py
  • tests/test_optimizer_assistant.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_optimizer_assistant.py

📝 Walkthrough

Walkthrough

Adds CLAUDE_SMART_CLI_PATH resolution for the host Claude CLI, conditionally changes rollout flags for compat-bridge executables, updates the related environment comment, and extends tests for override, fallback, and bridge routing behavior.

Changes

Host CLI Path Override

Layer / File(s) Summary
CLI path resolution implementation
plugin/src/claude_smart/optimizer_assistant.py
Adds the override constant, resolves the Claude CLI path from CLAUDE_SMART_CLI_PATH with PATH fallback, and conditionally omits rollout safety flags for known compat bridge executables.
Tests for override and fallback behavior
tests/test_optimizer_assistant.py
Adds coverage for override selection, missing-path failure, fallback to shutil.which, bridge wrapper execution, and direct _resolve_claude_cli_path() assertions.
Env config comment clarification
plugin/src/claude_smart/env_config.py
Expands the CLAUDE_SMART_USE_LOCAL_CLI_ENV comment to clarify 1/0 routing behavior and cloud LLM auto-detection.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • ReflexioAI/claude-smart#25: Both PRs modify host/CLI routing logic in optimizer_assistant.py via CLAUDE_SMART_CLI_PATH to select the backing executable.
  • ReflexioAI/claude-smart#99: Both PRs connect through CLAUDE_SMART_CLI_PATH and the opencode-claude-compat bridge, affecting how the CLI executable is resolved and invoked.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clear and accurately reflects the main change: honoring CLAUDE_SMART_CLI_PATH in optimizer CLI routing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@wenchanghan

Copy link
Copy Markdown
Contributor Author

Code review

Found 1 issue:

  1. The OpenCode compat bridge rejects the flags _run_claude() sends, so this fix will make every optimizer-assistant call fail on OpenCode hosts instead of routing correctly

On an OpenCode host, backend-service.sh sets CLAUDE_SMART_CLI_PATH to opencode-claude-compat.js, and _resolve_claude_cli_path() now returns that path verbatim. But _run_claude() unconditionally builds a command with --permission-mode, --tools, --disallowedTools, --no-session-persistence, --mcp-config, and --strict-mcp-config. opencode-claude-compat.js's parseSupportedArgs() only recognizes -p, --output-format, --model, --verbose, --include-partial-messages, and --append-system-prompt — any other argument throws unsupported Claude CLI argument: ${arg} and the bridge exits 1. Before this PR, _run_claude() always resolved the real claude binary directly (which does support these flags), so the incompatibility is newly introduced by routing through the bridge here. The new tests only mock subprocess.run and never exercise the bridge's real argument parsing, so this isn't caught by the added coverage.

def _run_claude(*, prompt: str, system_prompt: str) -> str:
cli_path = _resolve_claude_cli_path()
# This is an evaluation rollout, not a real user session: allow local
# inspection, but prevent filesystem, shell, MCP, and session mutations.
cmd = [
cli_path,
"-p",
"--output-format",
"json",
"--permission-mode",
"plan",
"--tools",
_READ_ONLY_TOOLS,
"--disallowedTools",
_MUTATING_TOOLS,
"--no-session-persistence",
"--mcp-config",
'{"mcpServers": {}}',
"--strict-mcp-config",
]

Bridge allow-list for reference:

function parseSupportedArgs(argv) {
let outputFormat = "json";
let systemPrompt = "";
let model = "";
let idx = 0;
while (idx < argv.length) {
const arg = argv[idx];
if (arg === "-p") {
idx += 1;
} else if (arg === "--output-format") {
outputFormat = requireValue(argv, idx, arg);
idx += 2;
} else if (arg === "--model") {
model = requireValue(argv, idx, arg);
idx += 2;
} else if (arg === "--verbose" || arg === "--include-partial-messages") {
idx += 1;
} else if (arg === "--append-system-prompt") {
systemPrompt = requireValue(argv, idx, arg);
idx += 2;
} else {
throw new Error(`unsupported Claude CLI argument: ${arg}`);
}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@wenchanghan

Copy link
Copy Markdown
Contributor Author

Addressed in 43cdff6.

What changed:

  • _run_claude() now detects claude-smart compatibility bridges (opencode-claude-compat* / codex-claude-compat*) and sends only the bridge-supported Claude-provider args.
  • Real Claude CLI invocations still keep the guarded optimizer flags (--permission-mode plan, read-only tools, disallowed mutating tools, no MCP, no session persistence).
  • Codex host behavior is now explicit in tests: optimizer rollouts use the native _run_codex() path and ignore CLAUDE_SMART_CLI_PATH.
  • Added a regression that runs through the real opencode-claude-compat parser with a fake OpenCode binary, so unsupported optimizer-only Claude flags cannot come back unnoticed.

Verified:

  • uvx ruff check plugin/src/claude_smart/optimizer_assistant.py tests/test_optimizer_assistant.py -> passed
  • uv run --project plugin pytest tests/test_optimizer_assistant.py tests/test_opencode_support.py::test_opencode_backend_service_uses_bridge_without_opencode_path_probe tests/test_codex_support.py::test_codex_hooks_use_expected_events_and_marketplace_fallback -> 14 passed
  • uv run --project plugin pytest -> 642 passed
  • Live OpenCode smoke with real opencode-claude-compat + local opencode 1.17.12 -> returned {"content":"OK"}
  • Live Codex smoke with local codex-cli 0.142.5 -> returned {"content":"OK"}

@yyiilluu yyiilluu merged commit 0dfc48d into ReflexioAI:main Jul 11, 2026
9 checks passed
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