Skip to content

feat: Claude Code CLI fallback for Max/Pro plan users#6

Open
PlutoYork wants to merge 2 commits intodbreunig:mainfrom
altpath-ai:feat/claude-cli-fallback
Open

feat: Claude Code CLI fallback for Max/Pro plan users#6
PlutoYork wants to merge 2 commits intodbreunig:mainfrom
altpath-ai:feat/claude-cli-fallback

Conversation

@PlutoYork
Copy link
Copy Markdown

Problem

Plumb requires ANTHROPIC_API_KEY for all LLM-powered features (spec parsing, decision extraction, test generation). Users on Claude Max or Pro plans authenticate via OAuth through Claude Code and don't have a separate API key. This means they can't use any of Plumb's LLM features out of the box.

As Claude Code adoption grows (especially Max plan users who are the most active AI-assisted developers), this is an increasing friction point for Plumb adoption.

Solution

Add a ClaudeCodeLM backend that routes DSPy inference through the claude -p CLI, using the existing OAuth session for authentication.

Resolution order (backward compatible):

  1. ANTHROPIC_API_KEY set → direct Anthropic API via LiteLLM (unchanged, still the fast path)
  2. claude CLI on PATH → ClaudeCodeLM fallback (new — zero config needed)
  3. Neither available → PlumbAuthError with clear instructions for both options

The fallback is transparent — no configuration flags, no .env changes. If Claude Code is installed and the user has an active session, Plumb detects it automatically via shutil.which("claude").

Implementation

  • ClaudeCodeLM(dspy.LM) — custom LM class that calls claude -p --model sonnet --output-format json --no-session-persistence as a subprocess
  • Parses the JSON event stream to extract the text response from the final "result" event
  • get_lm() now auto-detects the best available backend
  • validate_api_access() checks both paths and smoke-tests whichever is available

Trade-offs

  • Subprocess overhead: ~2-3s per call vs ~0.5s for direct API. Acceptable for commit-time hooks and spec parsing, but worth noting.
  • Dependency on Claude Code CLI: The fallback only activates when claude is on PATH. No new pip dependencies added.
  • Model default: CLI fallback uses sonnet (the alias), which resolves to the latest Sonnet via Claude Code's model routing.

Testing

Tested with Claude Code v2.1.76 on macOS with a Max plan subscription:

  • plumb parse-spec — successfully parsed 55 requirements from a spec file
  • plumb initvalidate_api_access() → smoke test passes
  • Falls back correctly when ANTHROPIC_API_KEY is unset
  • Uses direct API correctly when ANTHROPIC_API_KEY is set (no behavior change)

Scope

Single file changed: plumb/programs/__init__.py (+138, -18 lines)

Bruce Boston and others added 2 commits March 16, 2026 08:29
Users on Claude Max or Pro plans authenticate via OAuth through
Claude Code and don't have a separate ANTHROPIC_API_KEY. This adds
a ClaudeCodeLM backend that routes DSPy inference through the
`claude -p` CLI, enabling Plumb to work without an API key.

Resolution order:
1. ANTHROPIC_API_KEY set → direct Anthropic API via LiteLLM (unchanged)
2. `claude` CLI on PATH → ClaudeCodeLM fallback (new)
3. Neither → PlumbAuthError with clear instructions

The fallback is transparent — no configuration needed. If Claude Code
is installed and authenticated, Plumb detects it automatically.

Co-Authored-By: Bruce Boston <bruce@altpath.ai>
- Fix 2 failing tests that expected old ANTHROPIC_API_KEY-specific
  error messages (now generic "No LLM backend available")
- Mock _claude_code_available() in auth tests to isolate the
  "neither backend available" path
- Add new test: CLI fallback works when no API key but claude on PATH
- Add TestClaudeCodeLM (8 tests): instantiation, JSON event parsing,
  plain text fallback, timeout, missing CLI, nonzero exit, message
  building, callable delegation
- Add TestClaudeCodeAvailable (2 tests): PATH detection
- Add TestGetLm (4 tests): API key precedence, CLI fallback,
  nothing available, API key wins when both present

56 passed, 0 failed.

Co-Authored-By: Bruce Boston <bruce@altpath.ai>
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