Skip to content

feat(llm_cli): integrate Claude Code CLI as LLM_PROVIDER#1168

Merged
davincios merged 2 commits intoTracer-Cloud:mainfrom
Davidson3556:feature/claude-code-cli-integration
May 1, 2026
Merged

feat(llm_cli): integrate Claude Code CLI as LLM_PROVIDER#1168
davincios merged 2 commits intoTracer-Cloud:mainfrom
Davidson3556:feature/claude-code-cli-integration

Conversation

@Davidson3556
Copy link
Copy Markdown
Contributor

Fixes #1108

Describe the changes you have made in this PR -

Adds Anthropic Claude Code as a non-interactive CLI-backed LLM provider, selectable via LLM_PROVIDER=claude-code. The integration mirrors the existing OpenAI Codex CLI adapter shape end-to-end (adapter, registry, wizard option, env forwarding, fallback binary paths) so users can run opensre investigate against an alert using their local claude binary instead of an HTTP API key.

Highlights:

  • New ClaudeCodeAdapter in app/integrations/llm_cli/claude_code.py that runs claude -p --output-format text with the prompt on stdin — strictly non-interactive, no TTY, no approval loops.
  • Three-state auth probe (True / False / None) that checks ANTHROPIC_API_KEY env, then ~/.claude/.credentials.json. On macOS, when neither is present, returns None instead of False because claude login stores OAuth tokens in Keychain on darwin — a strict file-only check would falsely block Keychain-authenticated users.
  • Binary resolution via the shared resolve_cli_binary helper — CLAUDE_CODE_BIN override → PATH → npm/Homebrew/volta/pnpm/XDG fallbacks.
  • Provider registered in CLI_PROVIDER_REGISTRY and selectable through the onboarding wizard under "Local CLI providers".
  • 29 unit tests covering detect / build / parse / explain_failure, the CLAUDE_CODE_BIN override and PATH fallback, fallback paths on macOS / Linux / Windows (mocked sys.platform), registry registration, and env-forwarding security guarantees (specifically: ANTHROPIC_API_KEY is forwarded ONLY by the Claude Code invocation, not via the global allowlist, so credentials don't leak into Codex subprocesses).
  • .env.example documents CLAUDE_CODE_BIN and CLAUDE_CODE_MODEL.
  • runner.py: added CLAUDE_ to _SAFE_SUBPROCESS_ENV_PREFIXES so CLAUDE_CODE_* settings reach the subprocess; added USER and LOGNAME to safe keys (macOS Keychain item lookup needs USER).

Demo/Screenshot for feature changes and bug fixes -

Screenshot 2026-04-30 at 17 46 14 Screenshot 2026-04-30 at 18 07 57 Screenshot 2026-04-30 at 18 12 56

Code Understanding and AI Usage

Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?

  • No, I wrote all the code myself
  • Yes, I used AI assistance (continue below)

If you used AI assistance:

  • I have reviewed every single line of the AI-generated code
  • I can explain the purpose and logic of each function/component I added
  • I have tested edge cases and understand how the code handles them
  • I have modified the AI output to follow this project's coding standards and conventions

Explain your implementation approach:

Problem: OpenSRE supported Codex CLI as a non-interactive LLM provider but had no equivalent for Claude Code. Issue #1108 asks for the same shape: a one-shot, scriptable CLI invocation that runs to completion under subprocess.run with no TTY, no approval prompts, and no REPL.

Alternatives considered:

  1. Interactive mode (just claude with stdin) — rejected because the issue explicitly excludes interactive flows; the agent runtime can't sit in a REPL waiting for prompts.
  2. HTTP via Anthropic SDK — that path already exists as LLM_PROVIDER=anthropic. The point of this issue is to support users whose only auth is claude login (no API key on file), via subprocess to their local CLI.
  3. A subprocess auth probe (e.g. claude doctor) — rejected because Claude Code has no clean machine-readable auth-status command, so I went with a fast file-and-env check that doesn't fork a process for the probe at all.

Why this implementation:
The repo already has a clean LLMCLIAdapter Protocol and CLIBackedLLMClient runner that drive Codex. Implementing Claude Code as another adapter (instead of carving a new pathway) means I get caching of probes, ANSI stripping, env allowlisting, structured output, and timeout handling for free, and the change stays tightly scoped. Following the same shape is also explicitly requested by app/integrations/llm_cli/AGENTS.md.

Key components:

  • ClaudeCodeAdapter._resolve_binary() — defers to the shared resolve_cli_binary so CLAUDE_CODE_BIN override behaviour matches Codex (blank/invalid env value falls back instead of erroring).
  • ClaudeCodeAdapter._probe_binary() — runs claude --version with a generous 8s timeout (Codex's 3s was too tight: Claude Code does config/cache init at startup and times out when another claude process holds shared state). On version success, defers to _classify_claude_code_auth().
  • _classify_claude_code_auth() — env-var check first, then OAuth credentials file. The macOS branch deliberately returns None (not False) when both are absent, because Claude Code on darwin stores tokens in Keychain and the runner only blocks invocation on False; None lets the actual claude -p call surface the real auth state via explain_failure.
  • ClaudeCodeAdapter.build() — produces argv = (claude, -p, --output-format, text [, --model …]) with the prompt on stdin and NO_COLOR=1 in env. It explicitly forwards ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN here rather than via the global subprocess allowlist — this prevents Anthropic credentials from leaking into other CLI adapters' subprocesses (e.g. Codex).
  • parse() / explain_failure() — minimal, mirroring Codex; parse returns trimmed stdout, explain_failure includes the return code and a clipped stderr/stdout for the runner to surface.

Edge cases tested:

  • Binary missing entirely → clear install hint
  • CLAUDE_CODE_BIN points at a non-existent path → falls back to PATH
  • claude --version fails → installed=False with diagnostic detail
  • Missing creds on Linux → False (definitive); on macOS → None (Keychain may hold tokens)
  • Empty / None model → --model flag omitted (CLI default applies)
  • parse() with whitespace-only stdout
  • explain_failure() falls back to stdout when stderr is empty
  • Cross-OS fallback paths verified for macOS / Linux / Windows via patched sys.platform
  • Env-forwarding: CLAUDE_* reaches the subprocess; ANTHROPIC_API_KEY reaches Claude Code's subprocess only, not the global allowlist (so it isn't forwarded to Codex runs)

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 1, 2026

Greptile Summary

This PR adds ClaudeCodeAdapter — a non-interactive claude -p subprocess provider selectable via LLM_PROVIDER=claude-code — mirroring the existing Codex CLI adapter shape end-to-end (adapter, registry, wizard option, env forwarding, fallback binary paths). All integration points required by AGENTS.md are satisfied: the adapter is registered in CLI_PROVIDER_REGISTRY, the LLMProvider literal and config validators are updated, CLAUDE_ is added to the subprocess env prefix allowlist, and ANTHROPIC_API_KEY is forwarded explicitly via build() rather than the global allowlist so it cannot leak into Codex subprocesses.

Confidence Score: 5/5

Safe to merge; all findings are P2 style/documentation suggestions that do not affect runtime correctness.

The implementation cleanly follows the existing Codex adapter pattern, satisfies every item on the AGENTS.md provider checklist, correctly isolates ANTHROPIC_* credentials from other CLI subprocesses, and ships 29 unit tests covering the main paths. Only two P2 issues were found: a slightly inaccurate diagnostic string and a missing timeout test case.

app/integrations/llm_cli/claude_code.py — the darwin-branch detail string; tests/integrations/llm_cli/test_claude_code_adapter.py — missing TimeoutExpired test.

Important Files Changed

Filename Overview
app/integrations/llm_cli/claude_code.py New ClaudeCodeAdapter following the Codex shape; all protocol methods implemented, auth uses a file+env probe instead of a subprocess command. Minor: the darwin-branch detail string says "absent" even when the file exists but is nearly empty.
app/integrations/llm_cli/runner.py Adds USER/LOGNAME to safe env keys (needed for macOS Keychain item lookup) and CLAUDE_ prefix to subprocess env allowlist. Changes are minimal and well-reasoned.
app/integrations/llm_cli/registry.py Adds claude-code entry to CLI_PROVIDER_REGISTRY with correct adapter factory and model_env_key; consistent with existing codex entry.
app/cli/wizard/config.py Adds CLAUDE_CODE_MODELS tuple and ProviderOption for claude-code with credential_kind="cli", mirroring the codex entry correctly.
app/config.py Adds "claude-code" to LLMProvider Literal, the normalize_provider validator, and the API-key bypass list. All three spots updated correctly.
tests/integrations/llm_cli/test_claude_code_adapter.py 29 tests covering auth, detect, build, parse, explain_failure, BIN env override, cross-platform fallback paths, registry, and env-forwarding guarantees. Missing a dedicated TimeoutExpired test for _probe_binary.
.env.example Documents CLAUDE_CODE_MODEL and CLAUDE_CODE_BIN with install hint and adds claude-code to the LLM_PROVIDER comment. Accurate and complete.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([invoke prompt]) --> B[detect: claude --version]
    B -->|fails / timeout| C([RuntimeError: not installed])
    B -->|success| D{classify auth}
    D -->|env var set| E[logged_in = True]
    D -->|OAuth file present| E
    D -->|macOS, no file| F[logged_in = None]
    D -->|Linux/Win, no file| G[logged_in = False]
    G --> H([RuntimeError: run claude login])
    E --> I[build: argv + env overrides]
    F --> I
    I --> J[_build_subprocess_env: safe keys + CLAUDE_ prefix + overrides]
    J --> K[subprocess.run claude -p]
    K -->|rc != 0| L([RuntimeError: explain_failure])
    K -->|rc == 0| M([LLMResponse: stripped stdout])
Loading

Reviews (1): Last reviewed commit: "feat(llm_cli): integrate Claude Code CLI..." | Re-trigger Greptile

Comment thread app/integrations/llm_cli/claude_code.py
Comment thread tests/integrations/llm_cli/test_claude_code_adapter.py
@Davidson3556
Copy link
Copy Markdown
Contributor Author

Davidson3556 commented May 1, 2026

@davincios @VaibhavUpreti kindly review

@davincios davincios merged commit ea4d7ff into Tracer-Cloud:main May 1, 2026
10 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🤖 CI passed. Linter didn't scream. Reviewer typed LGTM. @Davidson3556, every machine in this pipeline just slow-clapped. 🖥️✨


👋 Join us on Discord - OpenSRE : hang out, contribute, or hunt for features and issues. Everyone's welcome.

@davincios
Copy link
Copy Markdown
Contributor

Done, congratulations on the great work 🎉 @Davidson3556

@Davidson3556
Copy link
Copy Markdown
Contributor Author

thank you. working on two other issues before I go to bed haha @davincios

Sarah-Salah added a commit to Sarah-Salah/opensre that referenced this pull request May 5, 2026
Resolves conflicts in auto-generated bot files: docs/daily-updates/overview.mdx, docs/daily-updates/2026-04-30.mdx, docs/daily-updates/2026-05-01.mdx, and README.md (contributors section). All four are produced by scheduled bot workflows in main and have no relationship to this PR; resolved by accepting the upstream main version verbatim.

Pulls in 35+ commits from main since this PR was opened, including: refactor of integrations module (Tracer-Cloud#1165), Splunk integration (Tracer-Cloud#791), interactive shell improvements (Tracer-Cloud#1159, Tracer-Cloud#1167), Claude Code CLI provider (Tracer-Cloud#1168), and CI quality gate restoration. None of these touch the OpenSearch wizard, detect_sources, or the validation modules this PR modifies.

Refs: Tracer-Cloud#1143
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.

[CLI] Integrate Claude Code CLI (non-interactive)

2 participants