diff --git a/.agents/scripts/supervisor-helper.sh b/.agents/scripts/supervisor-helper.sh index d47cb4405..4b571beab 100755 --- a/.agents/scripts/supervisor-helper.sh +++ b/.agents/scripts/supervisor-helper.sh @@ -111,18 +111,20 @@ unset _p # When `gh auth token` succeeds (interactive), cache it for cron to use later. _gh_token_cache="$HOME/.aidevops/.agent-workspace/supervisor/.gh-token-cache" if [[ -z "${GH_TOKEN:-}" ]]; then - # Try gh auth token (works interactively, fails in cron without keyring) - GH_TOKEN=$(gh auth token 2>/dev/null || echo "") + # Try cached token FIRST (most reliable for cron — written by interactive sessions) + if [[ -f "$_gh_token_cache" ]]; then + GH_TOKEN=$(cat "$_gh_token_cache" 2>/dev/null || echo "") + fi + # If no cache, try gh auth token (works interactively, may fail in cron) + if [[ -z "$GH_TOKEN" ]]; then + GH_TOKEN=$(gh auth token 2>/dev/null || echo "") + fi + # Cache the token if we got one (for future cron runs) if [[ -n "$GH_TOKEN" ]]; then - # Cache for cron (600 permissions — owner-only read/write) mkdir -p "$(dirname "$_gh_token_cache")" printf '%s' "$GH_TOKEN" > "$_gh_token_cache" 2>/dev/null || true chmod 600 "$_gh_token_cache" 2>/dev/null || true fi - if [[ -z "$GH_TOKEN" && -f "$_gh_token_cache" ]]; then - # Read from cache (written by a previous interactive session) - GH_TOKEN=$(cat "$_gh_token_cache" 2>/dev/null || echo "") - fi if [[ -z "$GH_TOKEN" ]]; then # Try gopass (encrypted secret store) GH_TOKEN=$(gopass show -o github/token 2>/dev/null || echo "") @@ -218,6 +220,19 @@ log_warn() { echo -e "${YELLOW}[SUPERVISOR]${NC} $*"; } log_error() { echo -e "${RED}[SUPERVISOR]${NC} $*" >&2; } log_verbose() { [[ "${SUPERVISOR_VERBOSE:-}" == "true" ]] && echo -e "${BLUE}[SUPERVISOR]${NC} $*" || true; } +# Check GitHub authentication in a way that works with GH_TOKEN env var. +# gh auth status may fail in cron even when GH_TOKEN is valid (keyring issues). +# This function checks GH_TOKEN first, then falls back to gh auth status. +check_gh_auth() { + # If GH_TOKEN is set, verify it works with a lightweight API call + if [[ -n "${GH_TOKEN:-}" ]]; then + gh api user --jq '.login' >/dev/null 2>&1 && return 0 + fi + # Fall back to gh auth status (works interactively with keyring) + gh auth status >/dev/null 2>&1 && return 0 + return 1 +} + # Supervisor stderr log file - captures stderr from commands that previously # used 2>/dev/null, making errors debuggable without cluttering terminal output. # See GH#441 (t144) for rationale. @@ -2511,7 +2526,7 @@ sync_claim_to_github() { # Skip if gh CLI not available or not authenticated command -v gh &>/dev/null || return 0 - gh auth status &>/dev/null 2>&1 || return 0 + check_gh_auth || return 0 local issue_number issue_number=$(find_task_issue_number "$task_id" "$project_root") @@ -3461,9 +3476,9 @@ cmd_dispatch() { # Pre-dispatch GitHub auth check — verify the worker can push before # creating worktrees and burning compute. Workers spawned via nohup/cron # may lack SSH keys; gh auth git-credential only works with HTTPS remotes. - if ! gh auth status >/dev/null 2>&1; then - log_error "GitHub auth unavailable for $task_id — gh auth status failed" - log_error "Workers need 'gh auth login' with HTTPS protocol. Skipping dispatch." + if ! check_gh_auth; then + log_error "GitHub auth unavailable for $task_id — check_gh_auth failed" + log_error "Workers need 'gh auth login' or GH_TOKEN set. Skipping dispatch." return 3 fi @@ -6388,7 +6403,7 @@ create_github_issue() { return 0 fi - if ! gh auth status &>/dev/null 2>&1; then + if ! check_gh_auth; then log_warn "gh CLI not authenticated, skipping GitHub issue creation" return 0 fi @@ -6644,7 +6659,7 @@ verify_task_deliverables() { log_warn "gh CLI not found; cannot verify deliverables for $task_id" return 1 fi - if ! gh auth status &>/dev/null 2>&1; then + if ! check_gh_auth; then log_warn "gh CLI not authenticated; cannot verify deliverables for $task_id" return 1 fi @@ -7287,7 +7302,7 @@ generate_verify_entry() { local files_list="" local -a check_lines=() - if [[ -n "$pr_number" ]] && command -v gh &>/dev/null && gh auth status &>/dev/null 2>&1; then + if [[ -n "$pr_number" ]] && command -v gh &>/dev/null && check_gh_auth; then local repo_slug="" repo_slug=$(detect_repo_slug "$trepo" 2>/dev/null || echo "") if [[ -n "$repo_slug" ]]; then diff --git a/setup.sh b/setup.sh index 9a5fa7fab..195e27bec 100755 --- a/setup.sh +++ b/setup.sh @@ -2575,9 +2575,11 @@ deploy_aidevops_agents() { if [[ "$CLEAN_MODE" == "true" ]]; then # Build list of directories to preserve: custom, draft, plus plugin namespaces local -a preserved_dirs=("custom" "draft") - for pns in "${plugin_namespaces[@]}"; do - preserved_dirs+=("$pns") - done + if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then + for pns in "${plugin_namespaces[@]}"; do + preserved_dirs+=("$pns") + done + fi print_info "Clean mode: removing stale files from $target_dir (preserving ${preserved_dirs[*]})" local tmp_preserve tmp_preserve="$(mktemp -d)" @@ -2617,18 +2619,22 @@ deploy_aidevops_agents() { local deploy_ok=false if command -v rsync &>/dev/null; then local -a rsync_excludes=("--exclude=loop-state/" "--exclude=custom/" "--exclude=draft/") - for pns in "${plugin_namespaces[@]}"; do - rsync_excludes+=("--exclude=${pns}/") - done + if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then + for pns in "${plugin_namespaces[@]}"; do + rsync_excludes+=("--exclude=${pns}/") + done + fi if rsync -a "${rsync_excludes[@]}" "$source_dir/" "$target_dir/"; then deploy_ok=true fi else # Fallback: use tar with exclusions to match rsync behavior local -a tar_excludes=("--exclude=loop-state" "--exclude=custom" "--exclude=draft") - for pns in "${plugin_namespaces[@]}"; do - tar_excludes+=("--exclude=$pns") - done + if [[ ${#plugin_namespaces[@]} -gt 0 ]]; then + for pns in "${plugin_namespaces[@]}"; do + tar_excludes+=("--exclude=$pns") + done + fi if (cd "$source_dir" && tar cf - "${tar_excludes[@]}" .) | (cd "$target_dir" && tar xf -); then deploy_ok=true fi