From 984823ccb12dc2274f7a19dfd0eca814f9087145 Mon Sep 17 00:00:00 2001 From: Zax Shen Date: Sun, 26 Apr 2026 12:04:53 -0700 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=94=8D=20debug(116):=20ad-hoc=20auth-?= =?UTF-8?q?isolation=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single job, no plugin, no env, no flags. Just: 1. Inspect token shape (length, whitespace detect, prefix) without exposing value 2. claude --version 3. claude -p "say hi in one word" — see if auth alone works If this passes → auth is fine; L6 failure is in plugin loading or persona trigger. If this fails → token is bad despite the right format. Triggers on PR open (path-filtered to this file) so we can run it without first merging to main. Will be deleted after diagnosis (#116). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/auth-diagnostic.yml | 74 +++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/auth-diagnostic.yml diff --git a/.github/workflows/auth-diagnostic.yml b/.github/workflows/auth-diagnostic.yml new file mode 100644 index 00000000..4ff10b15 --- /dev/null +++ b/.github/workflows/auth-diagnostic.yml @@ -0,0 +1,74 @@ +name: Auth diagnostic (one-off, delete after #116) + +# Ad-hoc workflow to isolate CLAUDE_CODE_OAUTH_TOKEN auth from everything +# else. No plugin, no env, no flags — just `claude -p "say hi"`. If THIS +# fails, the token itself is bad. If this passes, the failure is elsewhere. +# +# Security: only secrets routed via env:. No untrusted PR-injected strings. + +on: + workflow_dispatch: + # pull_request trigger so we can run this without first merging to main. + # The workflow runs from the PR's HEAD, so it picks up THIS file's content. + pull_request: + paths: + - '.github/workflows/auth-diagnostic.yml' + +jobs: + auth-only: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Setup Node 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install Claude Code CLI + run: | + npm install -g @anthropic-ai/claude-code + claude --version + + - name: Inspect token (length and shape only — never the value) + env: + TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + run: | + if [ -z "$TOKEN" ]; then + echo "FAIL: secret is empty" + exit 1 + fi + LEN=${#TOKEN} + echo "secret length: $LEN bytes" + # Detect trailing newline by comparing to length-stripped version + STRIPPED=$(printf '%s' "$TOKEN" | tr -d '\n\r\t ') + STRIPPED_LEN=${#STRIPPED} + DIFF=$((LEN - STRIPPED_LEN)) + if [ "$DIFF" -gt 0 ]; then + echo "WARNING: token has $DIFF whitespace/newline character(s) — likely the bug" + else + echo "no trailing whitespace detected (length matches stripped length)" + fi + # Token format prefix check (sk-ant-oat = OAuth) + PREFIX=$(printf '%s' "$TOKEN" | head -c 11) + # GitHub will mask the prefix in output too if it overlaps the secret + echo "first 11 chars (will be masked): $PREFIX" + + - name: Try basic claude -p with OAuth token + env: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + run: | + echo "Calling: claude -p 'say hi in one word'" + set +e + OUT=$(claude -p "say hi in one word" 2>&1) + RC=$? + set -e + echo "exit code: $RC" + echo "--- output (start) ---" + echo "$OUT" + echo "--- output (end) ---" + if [ $RC -eq 0 ] && [ -n "$OUT" ]; then + echo "AUTH WORKS" + else + echo "AUTH FAILED — see output above" + exit 1 + fi From b00293b618dccd0b7b17a0f97628a88709334f7a Mon Sep 17 00:00:00 2001 From: Zax Shen Date: Sun, 26 Apr 2026 12:09:25 -0700 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=A7=20ci:=20gate=20all=20CC-using?= =?UTF-8?q?=20workflows=20behind=20verify-cc-auth=20composite=20(#116)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per user direction: auth check should be a PREREQUISITE for any CI workflow that needs Claude Code. Fail-fast pattern — broken auth shouldn't burn Docker build minutes or test tokens. Changes: - New composite action .github/actions/verify-cc-auth/ - Inspects token shape (length, whitespace detect, OAuth/API prefix) without exposing the value - Installs Claude Code CLI (opt-out via input) - Runs `claude -p "say hi"` smoke test — fails with ::error:: annotation - l6-dogfood.yml: replaced inline check with the composite - l5-l6-combined.yml: same — runs BEFORE Docker build - Removed standalone auth-diagnostic.yml — superseded by composite Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/actions/verify-cc-auth/action.yml | 84 +++++++++++++++++++++++ .github/workflows/auth-diagnostic.yml | 74 -------------------- .github/workflows/l5-l6-combined.yml | 14 ++-- .github/workflows/l6-dogfood.yml | 22 +----- 4 files changed, 91 insertions(+), 103 deletions(-) create mode 100644 .github/actions/verify-cc-auth/action.yml delete mode 100644 .github/workflows/auth-diagnostic.yml diff --git a/.github/actions/verify-cc-auth/action.yml b/.github/actions/verify-cc-auth/action.yml new file mode 100644 index 00000000..614be28f --- /dev/null +++ b/.github/actions/verify-cc-auth/action.yml @@ -0,0 +1,84 @@ +name: 'Verify Claude Code auth' +description: | + Prerequisite check for any workflow that needs to invoke `claude` in CI. + Inspects the OAuth token shape, installs Claude Code if needed, runs + `claude -p "say hi"` to confirm auth works END-TO-END. + + Fail-fast pattern: if auth is broken (bad token, wrong format, network), + the consuming workflow's expensive steps (Docker builds, plugin + installs, multi-flow L6 runs) never start. + + Usage: + - uses: ./.github/actions/verify-cc-auth + with: + token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + +inputs: + token: + description: 'CLAUDE_CODE_OAUTH_TOKEN value (pass via secrets)' + required: true + install-cc: + description: 'Whether to install @anthropic-ai/claude-code first (default true)' + required: false + default: 'true' + +runs: + using: 'composite' + steps: + - name: Setup Node 22 (if not already) + if: inputs.install-cc == 'true' + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install Claude Code CLI + if: inputs.install-cc == 'true' + shell: bash + run: | + npm install -g @anthropic-ai/claude-code + claude --version + + - name: Inspect token shape (no value exposure) + shell: bash + env: + TOKEN: ${{ inputs.token }} + run: | + if [ -z "$TOKEN" ]; then + echo "::error::CLAUDE_CODE_OAUTH_TOKEN is empty" + exit 1 + fi + LEN=${#TOKEN} + STRIPPED=$(printf '%s' "$TOKEN" | tr -d '\n\r\t ') + STRIPPED_LEN=${#STRIPPED} + DIFF=$((LEN - STRIPPED_LEN)) + echo "Token length: $LEN bytes" + if [ "$DIFF" -gt 0 ]; then + echo "::error::Token contains $DIFF whitespace/newline char(s) — re-set with: TOKEN=\$(claude setup-token) && gh secret set CLAUDE_CODE_OAUTH_TOKEN --body \"\$TOKEN\"" + exit 1 + fi + # Sanity-check OAuth prefix + case "$TOKEN" in + sk-ant-oat-*) echo "Token has expected sk-ant-oat- prefix (OAuth)" ;; + sk-ant-api-*) echo "Token has sk-ant-api- prefix (API key — also valid)" ;; + *) echo "::warning::Token does not start with sk-ant-oat- or sk-ant-api- — unrecognized format" ;; + esac + + - name: Smoke-test auth with claude -p + shell: bash + env: + CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.token }} + run: | + echo "Calling: claude -p 'say hi in one word'" + set +e + OUT=$(timeout 30 claude -p "say hi in one word" 2>&1) + RC=$? + set -e + echo "exit code: $RC" + echo "--- output ---" + echo "$OUT" + echo "--- end output ---" + if [ $RC -ne 0 ] || [ -z "$OUT" ]; then + echo "::error::Auth smoke test failed — see output above. Token may be revoked, format wrong, or network down." + exit 1 + fi + echo "✓ Auth verified — claude -p produced output and exited 0" diff --git a/.github/workflows/auth-diagnostic.yml b/.github/workflows/auth-diagnostic.yml deleted file mode 100644 index 4ff10b15..00000000 --- a/.github/workflows/auth-diagnostic.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Auth diagnostic (one-off, delete after #116) - -# Ad-hoc workflow to isolate CLAUDE_CODE_OAUTH_TOKEN auth from everything -# else. No plugin, no env, no flags — just `claude -p "say hi"`. If THIS -# fails, the token itself is bad. If this passes, the failure is elsewhere. -# -# Security: only secrets routed via env:. No untrusted PR-injected strings. - -on: - workflow_dispatch: - # pull_request trigger so we can run this without first merging to main. - # The workflow runs from the PR's HEAD, so it picks up THIS file's content. - pull_request: - paths: - - '.github/workflows/auth-diagnostic.yml' - -jobs: - auth-only: - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Setup Node 22 - uses: actions/setup-node@v4 - with: - node-version: '22' - - - name: Install Claude Code CLI - run: | - npm install -g @anthropic-ai/claude-code - claude --version - - - name: Inspect token (length and shape only — never the value) - env: - TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - run: | - if [ -z "$TOKEN" ]; then - echo "FAIL: secret is empty" - exit 1 - fi - LEN=${#TOKEN} - echo "secret length: $LEN bytes" - # Detect trailing newline by comparing to length-stripped version - STRIPPED=$(printf '%s' "$TOKEN" | tr -d '\n\r\t ') - STRIPPED_LEN=${#STRIPPED} - DIFF=$((LEN - STRIPPED_LEN)) - if [ "$DIFF" -gt 0 ]; then - echo "WARNING: token has $DIFF whitespace/newline character(s) — likely the bug" - else - echo "no trailing whitespace detected (length matches stripped length)" - fi - # Token format prefix check (sk-ant-oat = OAuth) - PREFIX=$(printf '%s' "$TOKEN" | head -c 11) - # GitHub will mask the prefix in output too if it overlaps the secret - echo "first 11 chars (will be masked): $PREFIX" - - - name: Try basic claude -p with OAuth token - env: - CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - run: | - echo "Calling: claude -p 'say hi in one word'" - set +e - OUT=$(claude -p "say hi in one word" 2>&1) - RC=$? - set -e - echo "exit code: $RC" - echo "--- output (start) ---" - echo "$OUT" - echo "--- output (end) ---" - if [ $RC -eq 0 ] && [ -n "$OUT" ]; then - echo "AUTH WORKS" - else - echo "AUTH FAILED — see output above" - exit 1 - fi diff --git a/.github/workflows/l5-l6-combined.yml b/.github/workflows/l5-l6-combined.yml index 7407f297..c15cf20e 100644 --- a/.github/workflows/l5-l6-combined.yml +++ b/.github/workflows/l5-l6-combined.yml @@ -29,16 +29,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Verify token secret is present - env: - TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - run: | - if [ -z "$TOKEN" ]; then - echo "::warning::CLAUDE_CODE_OAUTH_TOKEN repo secret not set." - echo "L0 install piece will run; L6 piece will skip cleanly." - else - echo "Token present — full L5+L6 combined run." - fi + - name: Verify CC auth (prerequisite — fail-fast before Docker build) + uses: ./.github/actions/verify-cc-auth + with: + token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - name: Read plugin version id: version diff --git a/.github/workflows/l6-dogfood.yml b/.github/workflows/l6-dogfood.yml index 2d927807..60bd32b3 100644 --- a/.github/workflows/l6-dogfood.yml +++ b/.github/workflows/l6-dogfood.yml @@ -31,21 +31,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Verify secret is present - env: - TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - run: | - if [ -z "$TOKEN" ]; then - echo "::warning::CLAUDE_CODE_OAUTH_TOKEN repo secret not set — L6 cannot run." - echo "Add the secret in Settings → Secrets and variables → Actions." - exit 0 - fi - echo "Secret present." - - - name: Setup Node 22 - uses: actions/setup-node@v4 + - name: Verify CC auth (prerequisite — fail-fast on broken token) + uses: ./.github/actions/verify-cc-auth with: - node-version: '22' + token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - name: Setup Bun uses: oven-sh/setup-bun@v2 @@ -53,11 +42,6 @@ jobs: - name: Install plugin deps + build dist/ run: bun install --frozen-lockfile - - name: Install Claude Code CLI - run: | - npm install -g @anthropic-ai/claude-code - claude --version - - name: Run L6 dogfood flows env: CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}