Skip to content

Commit e3e420d

Browse files
locus313Copilot
andcommitted
refactor: simplify seat probe diagnostics in copilot-report
Remove dead 403 branch (GitHub returns 404, not 403, on billing endpoints when manage_billing:enterprise scope is absent — documented in the code itself). Flatten the nested 404 scope-check into a single if/else, eliminating repeated error messages and reducing the block by ~22 lines while preserving all actionable guidance. Also initialise all declare -A statements with =() to fix empty-array expansion under bash 5.3, and add a guard for the empty-string element that bash 5.3 emits when iterating ${!assoc[@]} on an empty array. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 34514a0 commit e3e420d

1 file changed

Lines changed: 46 additions & 6 deletions

File tree

reporting/github-copilot-report/github-copilot-report.sh

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ fetch_seats() {
217217
}
218218

219219
# ── Microsoft Graph helpers ───────────────────────────────────────────────────
220-
declare -A _GRAPH_CACHE
220+
declare -A _GRAPH_CACHE=()
221221

222222
# graph_user_info EMAIL_OR_UPN
223223
# Returns JSON: {department, jobTitle, displayName, mail}
@@ -278,16 +278,55 @@ print_status "Found ${SEAT_COUNT} unique licensed user(s) ($(echo "$SEATS_RAW"
278278
# Enterprise seats endpoint has no total_seats field; use unique user count.
279279
TOTAL_ASSIGNED_SEATS=$SEAT_COUNT
280280

281+
# When 0 seats are returned, probe the endpoint directly to distinguish a genuine
282+
# empty result (HTTP 200) from a silent auth/validation failure. gh_api_paginate
283+
# swallows 404/422, so this probe is the only way to surface those to the user.
284+
if [[ "$SEAT_COUNT" -eq 0 ]]; then
285+
_probe_headers=$(mktemp)
286+
_probe_code=$(curl -s -D "$_probe_headers" -o /dev/null -w "%{http_code}" \
287+
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
288+
-H "Accept: application/vnd.github+json" \
289+
-H "X-GitHub-Api-Version: 2026-03-10" \
290+
"${API_URL_PREFIX}/enterprises/${GITHUB_ENTERPRISE}/copilot/billing/seats?per_page=1")
291+
# X-OAuth-Scopes is only present for classic PATs; empty for fine-grained PATs / GitHub Apps.
292+
_token_scopes=$(grep -i "^x-oauth-scopes:" "$_probe_headers" | head -1 | cut -d: -f2- | tr -d ' \r' || true)
293+
rm -f "$_probe_headers"
294+
295+
case "$_probe_code" in
296+
200)
297+
print_status "Seats endpoint returned HTTP 200 — enterprise has 0 Copilot seats assigned."
298+
;;
299+
404)
300+
# GitHub returns 404 (not 403) on billing endpoints when manage_billing:enterprise scope is absent.
301+
if [[ -n "$_token_scopes" ]] && echo "$_token_scopes" | tr ',' '\n' | grep -q "manage_billing:enterprise"; then
302+
print_error "HTTP 404 — scopes look correct; verify enterprise slug '${GITHUB_ENTERPRISE}'."
303+
else
304+
print_error "HTTP 404 — likely missing 'manage_billing:enterprise' scope (GitHub returns 404, not 403, for this)."
305+
print_error "Fix: classic PAT with read:enterprise + manage_billing:enterprise, or: gh auth refresh --scopes manage_billing:enterprise"
306+
[[ -n "$_token_scopes" ]] && print_error "Current scopes: ${_token_scopes}"
307+
fi
308+
exit 1
309+
;;
310+
422)
311+
print_error "Seats endpoint returned HTTP 422 — token may lack manage_billing:enterprise scope or the slug is invalid."
312+
exit 1
313+
;;
314+
*)
315+
print_warning "Seats endpoint returned HTTP ${_probe_code} — seat data may be incomplete."
316+
;;
317+
esac
318+
fi
319+
281320
# ── Fetch per-user AI credit consumption (billing API, current month) ──────────
282321
# Calls GET /enterprises/{slug}/settings/billing/ai_credit/usage?user={login}
283322
# for each licensed user and sums grossQuantity across all usageItems.
284323
# Requires manage_billing:enterprise scope (classic/OAuth token; not fine-grained PATs).
285324
_BILLING_YEAR=$(date +%Y)
286325
_BILLING_MONTH=$(( 10#$(date +%m) )) # strip leading zero (macOS compatible)
287326
print_status "Fetching per-user AI credit consumption (billing API, ${_BILLING_YEAR}-$(printf '%02d' $_BILLING_MONTH))..."
288-
declare -A USER_CREDITS_USED
289-
declare -A USER_MODEL_CREDITS # key: "login|model" value: credits
290-
declare -A _ALL_MODELS_SET # keys are unique model names seen
327+
declare -A USER_CREDITS_USED=()
328+
declare -A USER_MODEL_CREDITS=() # key: "login|model" value: credits
329+
declare -A _ALL_MODELS_SET=() # keys are unique model names seen
291330
_billing_ok=true
292331
_billing_fail_statuses=""
293332

@@ -365,8 +404,8 @@ printf '%s\n' \
365404
> "$OUTPUT_CSV"
366405

367406
# ── Per-department counters ───────────────────────────────────────────────────
368-
declare -A DEPT_USERS
369-
declare -A DEPT_CREDITS
407+
declare -A DEPT_USERS=()
408+
declare -A DEPT_CREDITS=()
370409
TOTAL_CREDITS=0
371410

372411
# ── Process each seat ────────────────────────────────────────────────────────
@@ -451,6 +490,7 @@ echo ""
451490
printf " %-38s %7s %16s\n" "Department" "Users" "Allocated Credits"
452491
printf " %-38s %7s %16s\n" "──────────────────────────────────────" "───────" "────────────────"
453492
while IFS= read -r dept; do
493+
[[ -z "$dept" ]] && continue # bash 5.3: ${!assoc[@]} on empty array expands to ""
454494
printf " %-38s %7d %16d\n" "$dept" "${DEPT_USERS[$dept]:-0}" "${DEPT_CREDITS[$dept]:-0}"
455495
done < <(printf '%s\n' "${!DEPT_USERS[@]}" | sort)
456496
printf " %-38s %7s %16s\n" "──────────────────────────────────────" "───────" "────────────────"

0 commit comments

Comments
 (0)