fix(sleep): make --bare conditional on ANTHROPIC_API_KEY (#68)
ClaudeCliBackend._call() and attempt_with_tools() hardcoded --bare, which skips Claude CLI's credential resolution. This broke subscription- token auth: every model call silently returned "Not logged in" and scored 0 — the user saw "baseline 0.0 → candidate 0.0, gate reject" with no indication of an auth failure. Fix: only pass --bare when ANTHROPIC_API_KEY is set. The remaining isolation flags (--disable-slash-commands, --disallowedTools, --exclude-dynamic-system-prompt-sections, clean temp cwd) already provide the needed isolation without --bare. Also adds _detect_cli_error() to log a warning when CLI output matches known auth error patterns, so auth failures surface loudly instead of deflating every score to 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -944,5 +944,67 @@ class TestCopilotBackend(unittest.TestCase):
|
||||
shutil.rmtree(stub_dir, ignore_errors=True)
|
||||
|
||||
|
||||
class TestClaudeCliBackendBare(unittest.TestCase):
|
||||
"""Issue #68: --bare must be conditional on ANTHROPIC_API_KEY."""
|
||||
|
||||
def test_bare_included_when_api_key_set(self):
|
||||
"""With ANTHROPIC_API_KEY, --bare should appear in the command."""
|
||||
from skillopt_sleep.backend import ClaudeCliBackend
|
||||
be = ClaudeCliBackend(claude_path="/usr/bin/false", timeout=5)
|
||||
with unittest.mock.patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-test"}):
|
||||
# We can't run the real CLI, but we can inspect cmd construction
|
||||
# by monkeypatching subprocess.run to capture the command.
|
||||
captured = {}
|
||||
def fake_run(cmd, **kwargs):
|
||||
captured["cmd"] = cmd
|
||||
class FakeProc:
|
||||
stdout = "hello"
|
||||
stderr = ""
|
||||
returncode = 0
|
||||
return FakeProc()
|
||||
with unittest.mock.patch("subprocess.run", side_effect=fake_run):
|
||||
be._call("test prompt")
|
||||
self.assertIn("--bare", captured["cmd"])
|
||||
|
||||
def test_bare_omitted_without_api_key(self):
|
||||
"""Without ANTHROPIC_API_KEY, --bare should NOT appear."""
|
||||
from skillopt_sleep.backend import ClaudeCliBackend
|
||||
be = ClaudeCliBackend(claude_path="/usr/bin/false", timeout=5)
|
||||
env = os.environ.copy()
|
||||
env.pop("ANTHROPIC_API_KEY", None)
|
||||
with unittest.mock.patch.dict(os.environ, env, clear=True):
|
||||
captured = {}
|
||||
def fake_run(cmd, **kwargs):
|
||||
captured["cmd"] = cmd
|
||||
class FakeProc:
|
||||
stdout = "hello"
|
||||
stderr = ""
|
||||
returncode = 0
|
||||
return FakeProc()
|
||||
with unittest.mock.patch("subprocess.run", side_effect=fake_run):
|
||||
be._call("test prompt")
|
||||
self.assertNotIn("--bare", captured["cmd"])
|
||||
|
||||
def test_cli_error_detected_and_logged(self):
|
||||
"""Auth errors in CLI output should trigger a warning."""
|
||||
from skillopt_sleep.backend import ClaudeCliBackend
|
||||
be = ClaudeCliBackend(claude_path="/usr/bin/false", timeout=5)
|
||||
captured = {}
|
||||
def fake_run(cmd, **kwargs):
|
||||
captured["cmd"] = cmd
|
||||
class FakeProc:
|
||||
stdout = "Not logged in · Please run /login"
|
||||
stderr = ""
|
||||
returncode = 0
|
||||
return FakeProc()
|
||||
with unittest.mock.patch.dict(os.environ, {}, clear=False):
|
||||
with unittest.mock.patch("subprocess.run", side_effect=fake_run):
|
||||
result = be._call("test prompt")
|
||||
# The error string is returned as output (backwards-compat)
|
||||
self.assertIn("Not logged in", result)
|
||||
# But it's also recorded for detection
|
||||
self.assertIn("Not logged in", getattr(be, "last_call_error", ""))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
Reference in New Issue
Block a user