|
3 | 3 | import time
|
4 | 4 | import logging
|
5 | 5 | import shutil
|
| 6 | +import subprocess |
6 | 7 | from typing import Any
|
7 | 8 |
|
8 | 9 | from metacoder.coders.base_coder import (
|
@@ -146,7 +147,17 @@ def run(self, input_text: str) -> CoderOutput:
|
146 | 147 | logger.info(f"🤖 Running command: {' '.join(command)}")
|
147 | 148 | # time the command
|
148 | 149 | start_time = time.time()
|
149 |
| - ao = self.run_process(command, env) |
| 150 | + |
| 151 | + # Catch CalledProcessError to handle Claude CLI non-zero exit codes |
| 152 | + # Claude CLI can return non-zero exit codes even for successful operations |
| 153 | + try: |
| 154 | + ao = self.run_process(command, env) |
| 155 | + except subprocess.CalledProcessError as e: |
| 156 | + # Extract stdout and stderr from the exception |
| 157 | + stdout_text = e.stdout if e.stdout else "" |
| 158 | + stderr_text = e.stderr if e.stderr else "" |
| 159 | + # Create CoderOutput manually so we can process the JSON output |
| 160 | + ao = CoderOutput(stdout=stdout_text, stderr=stderr_text) |
150 | 161 |
|
151 | 162 | # parse the jsonl output
|
152 | 163 | def parse_jsonl_line(text: str) -> dict[str, Any]:
|
@@ -239,5 +250,15 @@ def parse_jsonl_line(text: str) -> dict[str, Any]:
|
239 | 250 | ao.total_cost_usd = total_cost_usd
|
240 | 251 | ao.success = not is_error
|
241 | 252 | if not ao.success:
|
| 253 | + # Check for authentication issues and provide helpful error message |
| 254 | + result_text = ao.result_text or "" |
| 255 | + if ( |
| 256 | + "Invalid API key" in result_text |
| 257 | + or "Please run /login" in result_text |
| 258 | + ): |
| 259 | + raise ValueError( |
| 260 | + f"Claude authentication failed. Try setting ANTHROPIC_AUTH_TOKEN environment variable or run 'claude setup-token'. " |
| 261 | + f"For custom endpoints, also set ANTHROPIC_BASE_URL. Original error: {ao.stderr} // {ao}" |
| 262 | + ) |
242 | 263 | raise ValueError(f"Claude failed with error: {ao.stderr} // {ao}")
|
243 | 264 | return ao
|
0 commit comments