@@ -553,21 +553,56 @@ def __init__(self, model: str = "", claude_path: str = "claude", timeout: int =
553553 timeout = timeout )
554554 self .claude_path = claude_path
555555
556+ # Known CLI error prefixes that indicate auth or config failures.
557+ # When detected, we log a warning so the user doesn't mistake a
558+ # broken auth for "nothing to optimize" (issue #68).
559+ # Keep these specific to avoid false positives on normal model output.
560+ _CLI_ERROR_MARKERS = (
561+ "Not logged in" ,
562+ "Please run /login" ,
563+ "Authentication required" ,
564+ "Invalid API key" ,
565+ "Unauthorized: invalid x-api-key" ,
566+ )
567+
568+ def _detect_cli_error (self , stdout : str , stderr : str ) -> None :
569+ """Log a warning if CLI output looks like an auth/config error.
570+
571+ Only checks stderr and short stdout (< 300 chars) to avoid
572+ false-positives on legitimate model responses that mention
573+ auth-related terms.
574+ """
575+ import logging
576+ # Long stdout is almost certainly a real model response, not an error.
577+ check_stdout = stdout if len (stdout ) < 300 else ""
578+ combined = check_stdout + "\n " + stderr
579+ for marker in self ._CLI_ERROR_MARKERS :
580+ if marker in combined :
581+ logging .getLogger ("skillopt_sleep" ).warning (
582+ "Claude CLI returned a likely auth error: %s" ,
583+ combined [:200 ].replace ("\n " , " " ),
584+ )
585+ self .last_call_error = combined [:500 ]
586+ return
587+
556588 def _call (self , prompt : str , * , max_tokens : int = 1024 ) -> str :
557589 # Run ISOLATED so the ambient Claude Code environment does not leak into
558590 # the optimizer/target call. Critically, the user's GLOBAL skills
559591 # (~/.claude/skills) are injected regardless of cwd, so we must disable
560592 # them explicitly — without this, reflect/attempt sometimes reply with a
561593 # list of the user's installed skills instead of doing the task.
562594 # --bare skip hooks, LSP, plugins (minimal mode)
595+ # Only safe with ANTHROPIC_API_KEY auth;
596+ # breaks subscription-token auth (#68).
563597 # --disable-slash-commands disable all skills
564598 # --disallowedTools '*' no tool use
565599 # --exclude-dynamic-... drop per-machine cwd/env/memory/git sections
566600 # cwd=<clean temp> no project CLAUDE.md
567601 import tempfile
568- cmd = [
569- self .claude_path , "-p" , "--output-format" , "text" ,
570- "--bare" ,
602+ cmd = [self .claude_path , "-p" , "--output-format" , "text" ]
603+ if os .environ .get ("ANTHROPIC_API_KEY" ):
604+ cmd .append ("--bare" )
605+ cmd += [
571606 "--disable-slash-commands" ,
572607 "--disallowedTools" , "*" ,
573608 "--exclude-dynamic-system-prompt-sections" ,
@@ -588,7 +623,9 @@ def _call(self, prompt: str, *, max_tokens: int = 1024) -> str:
588623 shutil .rmtree (clean_cwd , ignore_errors = True )
589624 except Exception :
590625 pass
591- return (proc .stdout or "" ).strip ()
626+ out = (proc .stdout or "" ).strip ()
627+ self ._detect_cli_error (out , proc .stderr or "" )
628+ return out
592629
593630 def attempt_with_tools (self , task , skill , memory , tools ):
594631 # Expose a REAL, callable `search` tool (a shell shim that logs each
@@ -625,9 +662,11 @@ def attempt_with_tools(self, task, skill, memory, tools):
625662 f"# Task\n { task .intent } \n \n { task .context_excerpt } \n \n "
626663 "Return ONLY the final answer text."
627664 )
628- cmd = [
629- self .claude_path , "-p" , "--output-format" , "text" ,
630- "--bare" , "--disable-slash-commands" ,
665+ cmd = [self .claude_path , "-p" , "--output-format" , "text" ]
666+ if os .environ .get ("ANTHROPIC_API_KEY" ):
667+ cmd .append ("--bare" )
668+ cmd += [
669+ "--disable-slash-commands" ,
631670 "--allowedTools" , "Bash" ,
632671 "--exclude-dynamic-system-prompt-sections" ,
633672 ]
@@ -639,6 +678,7 @@ def attempt_with_tools(self, task, skill, memory, tools):
639678 cmd , capture_output = True , text = True , timeout = self .timeout , cwd = work ,
640679 )
641680 resp = (proc .stdout or "" ).strip ()
681+ self ._detect_cli_error (resp , proc .stderr or "" )
642682 except Exception :
643683 resp = ""
644684 self ._tokens += len (prompt ) // 4 + len (resp ) // 4
0 commit comments