From 6a3919e5ae4cf7f8f6513638285acc706096e2d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 22:25:58 +0000 Subject: [PATCH] ci: add a temporary registry-auth debug workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The registry publish 401s at the edge upload, which authenticates with a hub-identity verifiable presentation — a different auth domain than the ATProto secrets the failure implicates at first glance. This dispatch-only workflow tests each domain alone, printing shapes and HTTP status codes and never values, so the two can be told apart from run output. Delete once publish auth works. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SZ4ZdaPo9erkh5yF4eXh3Z --- .github/workflows/debug-registry-auth.yml | 89 +++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .github/workflows/debug-registry-auth.yml diff --git a/.github/workflows/debug-registry-auth.yml b/.github/workflows/debug-registry-auth.yml new file mode 100644 index 0000000..03b6a1d --- /dev/null +++ b/.github/workflows/debug-registry-auth.yml @@ -0,0 +1,89 @@ +name: Debug registry auth + +# TEMPORARY — delete after the registry publish auth is sorted. Separates the release's two auth +# domains so each can be tested alone, printing only shapes and HTTP status codes, never values: +# - atproto: are ATPROTO_HANDLE / ATPROTO_APP_PASSWORD valid credentials for the publisher's PDS? +# - edge: which edge environments gate /registry/upload, which is where the release currently 401s +# (the upload authenticates with a hub-identity verifiable presentation, not the ATProto secrets). + +on: + workflow_dispatch: + +jobs: + atproto: + runs-on: ubuntu-latest + env: + ATPROTO_HANDLE: ${{ secrets.ATPROTO_HANDLE }} + ATPROTO_APP_PASSWORD: ${{ secrets.ATPROTO_APP_PASSWORD }} + steps: + - name: Secret shape (lengths and whitespace only) + run: | + node -e ' + const shape = (name) => { + const value = process.env[name] ?? ""; + console.log(name, JSON.stringify({ + set: value.length > 0, + length: value.length, + trimmed: value === value.trim(), + startsWithAt: value.startsWith("@"), + })); + }; + shape("ATPROTO_HANDLE"); + shape("ATPROTO_APP_PASSWORD"); + ' + + - name: Resolve the handle (public API) + id: resolve + run: | + handle=$(node -e 'process.stdout.write(encodeURIComponent((process.env.ATPROTO_HANDLE ?? "").trim()))') + status=$(curl -s -o /tmp/resolve.json -w '%{http_code}' "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}") + echo "resolveHandle: HTTP ${status} (200 = handle exists)" + did=$(node -e 'try { process.stdout.write(JSON.parse(require("fs").readFileSync("/tmp/resolve.json","utf8")).did ?? "") } catch {}') + echo "did: ${did:-}" + echo "did=${did}" >> "$GITHUB_OUTPUT" + + - name: Resolve the PDS (plc.directory) + id: pds + run: | + did='${{ steps.resolve.outputs.did }}' + pds="" + if [ -n "${did}" ]; then + curl -s "https://plc.directory/${did}" -o /tmp/plc.json || true + pds=$(node -e ' + try { + const doc = JSON.parse(require("fs").readFileSync("/tmp/plc.json","utf8")); + const service = (doc.service ?? []).find((entry) => entry.type === "AtprotoPersonalDataServer"); + process.stdout.write(service?.serviceEndpoint ?? ""); + } catch {} + ') + fi + echo "pds: ${pds:-}" + echo "pds=${pds:-https://bsky.social}" >> "$GITHUB_OUTPUT" + + - name: createSession (status only — 200 means the secrets are valid) + run: | + body=$(node -e 'process.stdout.write(JSON.stringify({ + identifier: (process.env.ATPROTO_HANDLE ?? "").trim(), + password: (process.env.ATPROTO_APP_PASSWORD ?? "").trim(), + }))') + status=$(curl -s -o /tmp/session.json -w '%{http_code}' -X POST \ + -H 'content-type: application/json' --data "${body}" \ + '${{ steps.pds.outputs.pds }}/xrpc/com.atproto.server.createSession') + echo "createSession: HTTP ${status}" + node -e ' + try { + const response = JSON.parse(require("fs").readFileSync("/tmp/session.json","utf8")); + console.log("error field:", response.error ?? "(none)"); + } catch {} + ' + + edge: + runs-on: ubuntu-latest + steps: + - name: Probe /registry/plugins (read) and /registry/upload (gated) per environment + run: | + for base in https://edge.dxos.workers.dev https://main.dxos.network https://dxos.network; do + read_status=$(curl -s -o /dev/null -w '%{http_code}' "${base}/registry/plugins" || echo 'ERR') + upload_status=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'content-type: application/json' --data '{}' "${base}/registry/upload" || echo 'ERR') + echo "${base} GET /registry/plugins: ${read_status} POST /registry/upload (unauthenticated): ${upload_status}" + done