@misterhuydo/sentinel 1.5.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -259,17 +259,19 @@ def _run_claude_attempt(
259
259
  cwd: str | None = None,
260
260
  claude_log_path: Path | None = None,
261
261
  on_progress=None,
262
+ cmd_override: list | None = None,
262
263
  ) -> tuple[str, bool]:
263
264
  """
264
265
  Run claude CLI with the given env. Returns (output, timed_out).
265
266
  Raises FileNotFoundError if binary is missing.
266
267
  If on_progress is given, calls on_progress(msg) for meaningful output lines
267
268
  (deduped — same message not repeated consecutively).
269
+ cmd_override: if provided, use this command list instead of _claude_cmd() default.
268
270
  """
269
271
  import threading as _threading
270
272
 
271
273
  proc = subprocess.Popen(
272
- _claude_cmd(bin_path, prompt),
274
+ cmd_override if cmd_override is not None else _claude_cmd(bin_path, prompt),
273
275
  stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
274
276
  text=True, env=env, cwd=cwd or None,
275
277
  )
@@ -171,12 +171,27 @@ def run_repo_task(
171
171
  if on_progress:
172
172
  on_progress(f":mag: Exploring `{repo.repo_name}`...")
173
173
 
174
+ import os as _os
175
+ skip_perms = _os.getuid() != 0
176
+ # OAuth attempt: no --bare so the stored OAuth session is used naturally
177
+ oauth_cmd = (
178
+ [cfg.claude_code_bin, "--dangerously-skip-permissions", "--print", prompt]
179
+ if skip_perms else
180
+ [cfg.claude_code_bin, "--print", prompt]
181
+ )
182
+ # API key fallback: --bare forces API-key-only auth
183
+ api_cmd = (
184
+ [cfg.claude_code_bin, "--dangerously-skip-permissions", "--bare", "--print", prompt]
185
+ if skip_perms else
186
+ [cfg.claude_code_bin, "--bare", "--print", prompt]
187
+ )
188
+ api_env = {**env, "ANTHROPIC_API_KEY": cfg.anthropic_api_key} if cfg.anthropic_api_key else None
189
+
174
190
  try:
175
191
  output, timed_out = _run_claude_attempt(
176
192
  cfg.claude_code_bin, prompt, env,
177
- cwd=local_path,
178
- claude_log_path=claude_log,
179
- on_progress=on_progress,
193
+ cwd=local_path, claude_log_path=claude_log, on_progress=on_progress,
194
+ cmd_override=oauth_cmd,
180
195
  )
181
196
  except FileNotFoundError:
182
197
  return "error", f"Claude binary not found: {cfg.claude_code_bin}"
@@ -187,7 +202,26 @@ def run_repo_task(
187
202
  stripped = output.strip()
188
203
 
189
204
  if _is_auth_error(stripped):
190
- return "error", "Claude authentication errorcheck API key or run `claude login`."
205
+ # Silent fallback: OAuth session expired retry with API key
206
+ if api_env:
207
+ logger.warning("repo_task/%s: OAuth session issue — retrying with API key", repo.repo_name)
208
+ if on_progress:
209
+ on_progress(":key: Auth refreshed — retrying...")
210
+ try:
211
+ output, timed_out = _run_claude_attempt(
212
+ cfg.claude_code_bin, prompt, api_env,
213
+ cwd=local_path, claude_log_path=claude_log, on_progress=on_progress,
214
+ cmd_override=api_cmd,
215
+ )
216
+ except FileNotFoundError:
217
+ return "error", f"Claude binary not found: {cfg.claude_code_bin}"
218
+ if timed_out:
219
+ return "error", "Claude timed out after 15 minutes."
220
+ stripped = output.strip()
221
+ if _is_auth_error(stripped):
222
+ return "error", "Claude authentication failed — check ANTHROPIC_API_KEY."
223
+ else:
224
+ return "error", "Claude authentication error — run `claude login` on the server or set ANTHROPIC_API_KEY."
191
225
 
192
226
  if stripped.upper().startswith("SKIP:"):
193
227
  reason = stripped[5:].strip()
@@ -2929,23 +2929,30 @@ async def _run_tool(name: str, inputs: dict, cfg_loader, store, slack_client=Non
2929
2929
  f"Question / Task: {question}"
2930
2930
  )
2931
2931
  from .fix_engine import _is_auth_error
2932
- cmd = (
2932
+ skip_perms = os.getuid() != 0
2933
+ # OAuth attempt: no --bare so the stored OAuth session is used naturally
2934
+ oauth_cmd = (
2935
+ [cfg.claude_code_bin, "--dangerously-skip-permissions", "--print", prompt]
2936
+ if skip_perms else
2937
+ [cfg.claude_code_bin, "--print", prompt]
2938
+ )
2939
+ # API key fallback: --bare forces API-key-only auth (no OAuth/hooks)
2940
+ api_cmd = (
2933
2941
  [cfg.claude_code_bin, "--dangerously-skip-permissions", "--bare", "--print", prompt]
2934
- if os.getuid() != 0 else
2942
+ if skip_perms else
2935
2943
  [cfg.claude_code_bin, "--bare", "--print", prompt]
2936
2944
  )
2937
2945
  run_kwargs = dict(capture_output=True, text=True, timeout=300,
2938
2946
  cwd=str(local_path), stdin=subprocess.DEVNULL)
2939
2947
  try:
2940
- # Try OAuth first (or API key if claude_pro_for_tasks=False)
2941
- r = subprocess.run(cmd, env=env, **run_kwargs)
2948
+ r = subprocess.run(oauth_cmd, env=env, **run_kwargs)
2942
2949
  output = (r.stdout or "").strip()
2943
2950
  auth_failed = _is_auth_error(output) or _is_auth_error(r.stderr or "")
2944
2951
 
2945
- # Silent fallback: retry with API key if OAuth session expired
2952
+ # Silent fallback: OAuth session expired — retry with API key
2946
2953
  if auth_failed and api_env:
2947
2954
  logger.warning("ask_codebase/%s: OAuth session issue — retrying with API key", repo_name)
2948
- r = subprocess.run(cmd, env=api_env, **run_kwargs)
2955
+ r = subprocess.run(api_cmd, env=api_env, **run_kwargs)
2949
2956
  output = (r.stdout or "").strip()
2950
2957
 
2951
2958
  logger.info("Boss ask_codebase %s mode=%s rc=%d len=%d", repo_name, mode, r.returncode, len(output))