Skip to content

fix(projects): authenticate GitHub fetch and fail builds loudly - #73

Merged
schmug merged 1 commit into
mainfrom
claude/fix-missing-repos-9GK61
Apr 29, 2026
Merged

schmug merged 1 commit into
mainfrom
claude/fix-missing-repos-9GK61

Conversation

@schmug

@schmug schmug commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

Live /api/projects.json was shipping with repos: [], so cortech.online's Projects view showed only the manual school-calendar fallback. Cloudflare Pages' shared build IPs were hitting GitHub's 60/hr unauthenticated rate limit, and fetchAllRepos silently swallowed the 403 into an empty list — the build succeeded, the deploy went out, and the regression was invisible.

Two paired changes:

  • Authenticate the build-time fetch when GITHUB_TOKEN is present. Read via process.env (server-only — never bundled to the client) and sent as Authorization: Bearer …. Lifts the limit to 5000/hr authenticated. Token is optional in dev/local builds.
  • Throw loudly on any failure — non-OK response or network error. Removes the silent-degrade pattern that let an empty deploy ship. Error message includes the GITHUB_TOKEN hint when the request was unauthenticated.

Defense in depth: explicitly filter r.private at the source in fetchAllRepos so a future endpoint or token-scope change can't accidentally leak private repos into the static JSON.

CI's npm run build step now passes the auto-injected secrets.GITHUB_TOKEN so verify jobs don't get rate-limited. README documents the new required env var for Cloudflare Pages: fine-grained PAT (or classic PAT with public_repo read), no broader scopes.

Tests inverted from the old silent-failure contract: now assert throws on 403 / network error, plus new coverage for Authorization header presence/absence and the private-repo filter.

Test plan

  • npm run format:check
  • npm run lint
  • npm run typecheck
  • npm test — 89/89 passing, including 6 new/updated tests in src/lib/github.test.ts
  • Verified npm run build does now hard-fail with the actionable error message when no token is set and GitHub responds 403 (was previously the silent regression)
  • After merge: confirm live https://cortech.online/api/projects.json returns a non-empty repos array with current repos
  • After merge: confirm the Projects page on the live site renders all non-fork/non-archived repos again

Operator action required

Add GITHUB_TOKEN as an encrypted env var in Cloudflare Pages → Settings → Environment variables. (User confirmed this is now done — classic PAT with public-repo read scoped to cortech-online.)

https://claude.ai/code/session_01QAwJ34JC732rN13zxJHzuk


Generated by Claude Code

Live /api/projects.json was shipping with repos: [] because Cloudflare
Pages' shared build IPs hit GitHub's 60/hr unauthenticated rate limit,
and fetchAllRepos silently swallowed the 403 into an empty list. Result:
no dynamically-fetched repos appeared on cortech.online — only the
manual school-calendar fallback.

Two paired changes:

- Authenticate the build-time fetch when GITHUB_TOKEN is present
  (process.env, server-only — never bundled to the client). Lifts the
  rate limit to 5000/hr authenticated. Token is optional in dev so
  local builds still work when below the unauth quota.

- Replace the silent-failure contract with a hard throw on any non-OK
  response or network error, so a transient GitHub hiccup blocks the
  deploy instead of shipping a degraded site. Error message includes
  the GITHUB_TOKEN hint when unauthenticated.

Defense in depth: explicitly filter r.private at the source so a
future endpoint or token-scope change can't accidentally leak private
repos into the static JSON.

CI build step now passes the auto-injected secrets.GITHUB_TOKEN.
README documents the new required env var for Cloudflare Pages —
fine-grained PAT, public-repo read-only, no other scopes.

Tests inverted: the silent-failure contract is replaced with throw
assertions, plus new coverage for Authorization header presence/absence
and the private-repo filter.
@schmug
schmug merged commit b56a240 into main Apr 29, 2026
2 checks passed
@schmug
schmug deleted the claude/fix-missing-repos-9GK61 branch April 29, 2026 18:04
schmug added a commit that referenced this pull request Apr 30, 2026
Two related guardrails so the build can't silently ship a half-broken
deploy:

- Add scripts/check-deps.mjs wired as predev/prebuild. Runs `npm install`
  only when package-lock.json is newer than node_modules/.package-lock.json.
  Skipped on CI (which uses `npm ci` explicitly) and via SKIP_DEP_CHECK=1.
  Closes #23 — fixes the @astrojs/rss "Rollup failed to resolve" error
  that bites worktree switches when a dep was added on main.

- Throw in /api/projects.json when fetchAllRepos returns 0 repos.
  Defense-in-depth on top of #73: even if a future change ever lets the
  fetcher return an empty list cleanly, the build now fails instead of
  publishing a blank desktop. Add a regression test against the live
  featuredRepos config locking in which manual-fallback entries survive
  an empty API response. Closes the remaining items on #27.

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants