From 6145f409506aed9a5e2ee8dcd2bd0e9e9406a77f Mon Sep 17 00:00:00 2001 From: Lukas Wodka Date: Sun, 26 Jul 2026 16:10:09 +0200 Subject: [PATCH 1/2] fix(sync-docs): diagnosable upstream fetch; name the missing token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sync has failed 13/13 runs, dying ~2s in with a bare 'gh: Not Found (HTTP 404)'. Root cause: SOURCE_REPOS_TOKEN is unset, so the fetch falls back to the docs-scoped GITHUB_TOKEN, which 404s on the first PRIVATE source (tracebloc-py-package). 'jq | while' ran in a subshell and 'set -e' killed it on the first source, hiding the other four and the reason. Harden the fetch step: - process substitution instead of 'jq | while', so the loop runs in this shell - fetch every source and collect failures instead of dying on the first - on failure, emit '::error::' naming the repo and the gh message; drop the truncated cache file so a partial file is never synced - when SOURCE_REPOS_TOKEN is unset, print the exact fix: create a fine-grained PAT with Contents:read on the private source repo(s) and add it as SOURCE_REPOS_TOKEN Still fails closed until the token exists (a private source must sync), but now says precisely why and which token. Verified the loop with a stubbed gh: public sources cache, the private one reports a named error, exit 1 with guidance. The token itself must be created by an admin — that is the actual unblock. Refs RFC-BACKEND-0008 D21, tracebloc/backend#1279. --- .github/workflows/sync-docs.yml | 36 +++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sync-docs.yml b/.github/workflows/sync-docs.yml index 6f5f431..aec9852 100644 --- a/.github/workflows/sync-docs.yml +++ b/.github/workflows/sync-docs.yml @@ -88,19 +88,43 @@ jobs: if: steps.filter.outputs.count != '0' env: GH_TOKEN: ${{ secrets.SOURCE_REPOS_TOKEN || secrets.GITHUB_TOKEN }} + # true only when the PAT actually exists — used to give an actionable + # error, without ever exposing the secret value. + HAS_SOURCE_TOKEN: ${{ secrets.SOURCE_REPOS_TOKEN != '' && 'yes' || 'no' }} run: | mkdir -p /tmp/sync-cache - jq -c '.[]' /tmp/sources.json | while read -r s; do + failed="" + # Fetch every source, then report — don't die on the first one. The + # default GITHUB_TOKEN is scoped to THIS repo, so a private source + # (e.g. tracebloc-py-package) 404s unless SOURCE_REPOS_TOKEN is set; + # dying on the first source hid the other four and produced only a + # cryptic `gh: Not Found`. Process substitution (not `jq | while`) so + # the loop runs in this shell and `failed` survives. + while read -r s; do id=$(jq -r .id <<<"$s") repo=$(jq -r .repo <<<"$s") ref=$(jq -r .ref <<<"$s") src=$(jq -r .src <<<"$s") echo "Fetching $repo@$ref:$src -> /tmp/sync-cache/$id" - gh api "repos/$repo/contents/$src?ref=$ref" \ - -H "Accept: application/vnd.github.raw" \ - > "/tmp/sync-cache/$id" - done - ls -la /tmp/sync-cache + if gh api "repos/$repo/contents/$src?ref=$ref" \ + -H "Accept: application/vnd.github.raw" \ + > "/tmp/sync-cache/$id" 2>/tmp/gh-err; then + continue + fi + rm -f "/tmp/sync-cache/$id" # don't sync a truncated/empty file + echo "::error::Could not fetch ${repo}@${ref}:${src} — $(tr -d '\n' < /tmp/gh-err)" + failed="$failed ${id}(${repo})" + done < <(jq -c '.[]' /tmp/sources.json) + + ls -la /tmp/sync-cache || true + + if [ -n "$failed" ]; then + echo "::error::Upstream fetch failed for:$failed" + if [ "$HAS_SOURCE_TOKEN" = "no" ]; then + echo "::error::SOURCE_REPOS_TOKEN is not set. Private sources are then read with the docs-scoped GITHUB_TOKEN, which 404s cross-repo. Create a fine-grained PAT with Contents:read on the private source repo(s) — currently tracebloc/tracebloc-py-package — and add it as the SOURCE_REPOS_TOKEN Actions secret on this repo (or the org)." + fi + exit 1 + fi - name: Run Claude to update docs if: steps.filter.outputs.count != '0' From e533e85e805c53f245679654fc23984634520428 Mon Sep 17 00:00:00 2001 From: Lukas Wodka Date: Sun, 26 Jul 2026 16:46:47 +0200 Subject: [PATCH 2/2] ci: add advance-deploy-env caller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs had no advance-deploy-env caller, so its kanban items never advanced to Prod when code shipped to main — feature tickets stranded. Adds the standard push-triggered caller the other repos have, so shipping auto-advances tickets. RFC-BACKEND-0008 board automation. --- .github/workflows/advance-deploy-env.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/advance-deploy-env.yml diff --git a/.github/workflows/advance-deploy-env.yml b/.github/workflows/advance-deploy-env.yml new file mode 100644 index 0000000..3e8d7b9 --- /dev/null +++ b/.github/workflows/advance-deploy-env.yml @@ -0,0 +1,14 @@ +name: Advance deploy env + +# Calls the reusable workflow in tracebloc/.github. +# When this branch (develop/staging/master/main) receives a merge, +# updates each contained PR's Deploy environment field on the engineer kanban. + +on: + push: + branches: [develop, staging, master, main] + +jobs: + advance: + uses: tracebloc/.github/.github/workflows/advance-deploy-env.yml@main + secrets: inherit