From 055262e334f8227392a99eb00fb6605d52176a18 Mon Sep 17 00:00:00 2001 From: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:48:01 +0000 Subject: [PATCH 1/2] feat(#256): add GH_TOKEN validation to pre-retro.sh Validate GH_TOKEN with gh auth status before the sandbox starts. When the token is invalid, exit 1 with an ::error:: annotation, saving the cost of a full agent run (model inference, sandbox creation, teardown). When GH_TOKEN is unset, emit a ::warning:: and continue with limited functionality. This follows the pattern from pre-review.sh which performs token-dependent pre-flight checks. Closes #256 --- .../scaffold/fullsend-repo/scripts/pre-retro.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/scaffold/fullsend-repo/scripts/pre-retro.sh b/internal/scaffold/fullsend-repo/scripts/pre-retro.sh index 9ee8b9eab..87ed36b4a 100755 --- a/internal/scaffold/fullsend-repo/scripts/pre-retro.sh +++ b/internal/scaffold/fullsend-repo/scripts/pre-retro.sh @@ -8,6 +8,7 @@ # ORIGINATING_URL — HTML URL of the PR or issue that triggered retro # # Optional env vars: +# GH_TOKEN — GitHub token; validated if set, warning if absent # RETRO_COMMENT — The /retro comment text (empty for automatic triggers) set -euo pipefail @@ -22,6 +23,19 @@ fi echo "::notice::Retro target: ${ORIGINATING_URL}" +# --------------------------------------------------------------------------- +# Validate GH_TOKEN before starting the sandbox +# --------------------------------------------------------------------------- +if [[ -n "${GH_TOKEN:-}" ]]; then + if ! gh auth status 2>/dev/null; then + echo "::error::GH_TOKEN is invalid — retro agent requires GitHub API access" + exit 1 + fi + echo "GH_TOKEN validated successfully." +else + echo "::warning::GH_TOKEN is not set — retro agent will have limited functionality" +fi + if [[ -n "${RETRO_COMMENT:-}" ]]; then echo "Retro triggered on-demand with comment." else From 3420f45c61b0567f470f8015d6474547648eb9ef Mon Sep 17 00:00:00 2001 From: fullsend-fix <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:45:09 +0000 Subject: [PATCH 2/2] fix(#256): make GH_TOKEN required and harden validation in pre-retro.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fail hard (exit 1) when GH_TOKEN is unset instead of warning and continuing — prevents wasting a full sandbox run that will always fail in post-retro.sh (closes the fail-fast gap from issue #256) - Move GH_TOKEN from optional to required in header comment - Add ::add-mask:: to redact token from CI logs (defense-in-depth) - Suppress gh auth status stdout (>/dev/null 2>&1) - Add comment noting gh auth status checks auth, not scopes - Add pre-retro-test.sh covering valid/invalid/absent token paths Addresses review feedback on #262 --- .../fullsend-repo/scripts/pre-retro-test.sh | 129 ++++++++++++++++++ .../fullsend-repo/scripts/pre-retro.sh | 22 +-- 2 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 internal/scaffold/fullsend-repo/scripts/pre-retro-test.sh diff --git a/internal/scaffold/fullsend-repo/scripts/pre-retro-test.sh b/internal/scaffold/fullsend-repo/scripts/pre-retro-test.sh new file mode 100644 index 000000000..b70b37157 --- /dev/null +++ b/internal/scaffold/fullsend-repo/scripts/pre-retro-test.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# pre-retro-test.sh — Test pre-retro.sh with mock gh to verify token validation. +# +# Uses a mock gh command to capture calls without hitting GitHub. +# Run from the repo root: bash internal/scaffold/fullsend-repo/scripts/pre-retro-test.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PRE_SCRIPT="${SCRIPT_DIR}/pre-retro.sh" +FAILURES=0 + +# Create a temp directory for mock state. +TMPDIR="$(mktemp -d)" +trap 'rm -rf "${TMPDIR}"' EXIT + +# --- Helpers --- + +# build_mock creates a mock gh binary that returns a preconfigured exit code +# for "gh auth status" calls. +# Arguments: +# $1 — exit code for "gh auth status" (0 = valid token, 1 = invalid token). +build_mock() { + local auth_exit="$1" + local mock_bin="${TMPDIR}/bin" + + rm -rf "${mock_bin}" + mkdir -p "${mock_bin}" + + cat > "${mock_bin}/gh" < "${TMPDIR}/stdout.log" 2>&1 || exit_code=$? + + if [[ ${exit_code} -ne ${expect_exit} ]]; then + echo "FAIL: ${test_name} — expected exit ${expect_exit}, got ${exit_code}" + cat "${TMPDIR}/stdout.log" + FAILURES=$((FAILURES + 1)) + return + fi + + if ! grep -qF "${expected_stdout}" "${TMPDIR}/stdout.log" 2>/dev/null; then + echo "FAIL: ${test_name} — expected stdout '${expected_stdout}' not found" + echo "Actual stdout:" + cat "${TMPDIR}/stdout.log" + FAILURES=$((FAILURES + 1)) + return + fi + + echo "PASS: ${test_name}" +} + +# --- Test cases --- + +# Valid token → auth status exits 0 → script succeeds. +run_test_stdout "valid-token-succeeds" \ + 0 \ + "GH_TOKEN validated successfully." \ + 0 \ + "GH_TOKEN=fake-token" + +# Invalid token → auth status exits 1 → script fails with error annotation. +run_test_stdout "invalid-token-fails" \ + 1 \ + "::error::GH_TOKEN is invalid" \ + 1 \ + "GH_TOKEN=bad-token" + +# Absent token → script fails with error annotation (no sandbox wasted). +run_test_stdout "absent-token-fails" \ + 0 \ + "::error::GH_TOKEN is not set" \ + 1 + +# Valid token with retro comment → script succeeds with on-demand message. +run_test_stdout "retro-comment-on-demand" \ + 0 \ + "Retro triggered on-demand with comment." \ + 0 \ + "$(printf '%s\n%s' 'GH_TOKEN=fake-token' 'RETRO_COMMENT=look at this')" + +# Valid token without retro comment → script succeeds with automatic message. +run_test_stdout "retro-automatic-trigger" \ + 0 \ + "Retro triggered automatically (PR close)." \ + 0 \ + "GH_TOKEN=fake-token" + +# --- Summary --- + +echo "" +if [[ ${FAILURES} -gt 0 ]]; then + echo "${FAILURES} test(s) failed" + exit 1 +fi +echo "All tests passed" diff --git a/internal/scaffold/fullsend-repo/scripts/pre-retro.sh b/internal/scaffold/fullsend-repo/scripts/pre-retro.sh index 87ed36b4a..c64a902ad 100755 --- a/internal/scaffold/fullsend-repo/scripts/pre-retro.sh +++ b/internal/scaffold/fullsend-repo/scripts/pre-retro.sh @@ -6,9 +6,9 @@ # # Required env vars: # ORIGINATING_URL — HTML URL of the PR or issue that triggered retro +# GH_TOKEN — GitHub token; validated before sandbox starts # # Optional env vars: -# GH_TOKEN — GitHub token; validated if set, warning if absent # RETRO_COMMENT — The /retro comment text (empty for automatic triggers) set -euo pipefail @@ -26,15 +26,19 @@ echo "::notice::Retro target: ${ORIGINATING_URL}" # --------------------------------------------------------------------------- # Validate GH_TOKEN before starting the sandbox # --------------------------------------------------------------------------- -if [[ -n "${GH_TOKEN:-}" ]]; then - if ! gh auth status 2>/dev/null; then - echo "::error::GH_TOKEN is invalid — retro agent requires GitHub API access" - exit 1 - fi - echo "GH_TOKEN validated successfully." -else - echo "::warning::GH_TOKEN is not set — retro agent will have limited functionality" +# ponytail: gh auth status validates auth, not scopes — a token with only +# contents:read would pass here but fail in post-retro.sh. Still catches the +# #256 failure mode (expired/revoked token). Scope checks if needed later. +if [[ -z "${GH_TOKEN:-}" ]]; then + echo "::error::GH_TOKEN is not set — retro agent requires GitHub API access" + exit 1 +fi +echo "::add-mask::${GH_TOKEN}" +if ! gh auth status >/dev/null 2>&1; then + echo "::error::GH_TOKEN is invalid — retro agent requires GitHub API access" + exit 1 fi +echo "GH_TOKEN validated successfully." if [[ -n "${RETRO_COMMENT:-}" ]]; then echo "Retro triggered on-demand with comment."