Skip to content

fix(releases-proxy): switch to asset API endpoint + add observability#14

Merged
thinmintdev merged 1 commit into
masterfrom
fix/releases-proxy-observability-2026-05-22
May 22, 2026
Merged

fix(releases-proxy): switch to asset API endpoint + add observability#14
thinmintdev merged 1 commit into
masterfrom
fix/releases-proxy-observability-2026-05-22

Conversation

@thinmintdev
Copy link
Copy Markdown
Contributor

Summary

releases.hal0.dev/stable.json is silently returning the static _placeholder: true backstop despite Hal0ai/hal0@v0.1.0-alpha.1 shipping a real stable.json asset. Two changes to fix + diagnose.

Background

After PR #10 merged, manual verification showed:

$ curl -sS https://releases.hal0.dev/stable.json | head -5
{
  "_schema": "hal0.releases.v1",
  "_placeholder": true,
  "_note": "Pre-release placeholder. ...",

Upstream is healthy — api.github.com/repos/Hal0ai/hal0/releases returns v0.1.0-alpha.1 with the stable.json asset attached. So the proxy is failing somewhere inside proxyChannelManifest() and returning null, falling through to the static placeholder.

The problem: with the original code, we cannot tell from the outside whether the new middleware ever deployed vs. deployed-and-failed. Both produce byte-identical responses, because the static-placeholder response's cache-control: public, max-age=60, must-revalidate actually comes from public/_headers (not the proxy code), so even that header doesn't disambiguate.

What changed

1. Asset fetch via api.github.com asset endpoint

Switch from asset.browser_download_url to asset.url with Accept: application/octet-stream. Both ultimately land on objects.githubusercontent.com for the bytes, but the api.github.com asset endpoint is the documented direct-download path and is slightly more predictable for CF Workers' cross-origin redirect handling.

2. Observability on every failure branch

Each bailout point now:

Branch console.warn tag x-hal0-proxy-failed header value
Releases list fetch threw gh-list-threw:<err> same
Releases list non-2xx gh-list-<status> same
Releases list JSON parse gh-list-parse:<err> same
Asset fetch threw gh-asset-threw:<err>:<tag> same
Asset fetch non-2xx gh-asset-<status>:<tag> same
No release ships the asset no-asset:<channel>.json:<count>releases same

The console.warn calls show up in wrangler tail and the CF Pages logs UI. The x-hal0-proxy-failed response header is visible to any curl -I, so the next time this regresses we can diagnose without Cloudflare account access:

$ curl -sSI https://releases.hal0.dev/stable.json | grep x-hal0
x-hal0-source: github-release/v0.1.0-alpha.1    # success
# or, on failure:
x-hal0-proxy-failed: gh-list-403                # rate-limited on CF outbound IP
x-hal0-proxy-failed: gh-asset-403:v0.1.0-alpha.1  # asset download blocked

Out of scope

  • Auth via GITHUB_TOKEN to raise the 60/hr rate limit. If the logs surface that this is the actual failure mode, follow-up PR can add the secret to CF Pages env. Not done here because we don't know it's the issue yet.
  • @cloudflare/workers-types still not added. The local PagesFunctionContext type stays the same.
  • Cleanup of the stale releases.hal0.dev does not exist yet comment in Hal0ai/hal0:.github/workflows/release.yml lines 13–15 — different repo, separate PR.

Test plan

  • CI green (Vercel Preview, Cloudflare Pages, Vercel Agent Review)
  • After deploy, curl -sSI https://releases.hal0.dev/stable.json | grep x-hal0 shows either x-hal0-source: github-release/v0.1.0-alpha.1 (fix worked) or a specific x-hal0-proxy-failed: <reason> (fix didn't work but we now know exactly why)
  • If the success path fires, body should be the live v0.1.0-alpha.1 manifest with non-zero digest_sha256, not _placeholder: true

🤖 Generated with Claude Code

The releases.hal0.dev/{stable,nightly,dev}.json proxy was silently
falling through to the static `_placeholder: true` backstop despite
v0.1.0-alpha.1 shipping a real `stable.json` asset. Two changes:

1. **Switch asset fetch from `browser_download_url` to `asset.url`**
   with `Accept: application/octet-stream`. Both end up at
   `objects.githubusercontent.com`, but the api.github.com asset
   endpoint is the documented direct-download path and is slightly
   more reliable for cross-origin redirect handling from CF Workers.

2. **Add observability.** Every failure branch (list fetch threw,
   list non-2xx, list parse, asset fetch threw, asset non-2xx, no
   release with the named asset) now:
     - emits a `console.warn` with a stable reason tag, visible in
       `wrangler tail` and the CF Pages logs UI
     - propagates a `x-hal0-proxy-failed: <reason>` response header
       on the fallthrough static-placeholder response

The second piece is the meaningful change. Previously, an external
`curl https://releases.hal0.dev/stable.json` could not distinguish
"middleware never deployed" from "middleware deployed and failed".
Both produced byte-identical responses (the static placeholder body
with `_headers`-derived cache-control). With this PR, the header
tells you which code path executed and why it bailed — no Cloudflare
account access required to diagnose the next regression.

Returns nothing else changed: success path still sets the same
content-type / CORS / cache-control / x-hal0-source / x-hal0-channel
headers, and non-channel paths still take the original /releases/*
rewrite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hal0-web Ready Ready Preview, Comment May 22, 2026 5:04am

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying hal0-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: e6d302b
Status: ✅  Deploy successful!
Preview URL: https://be61482c.hal0-web.pages.dev
Branch Preview URL: https://fix-releases-proxy-observabi.hal0-web.pages.dev

View logs

@thinmintdev thinmintdev merged commit f42e274 into master May 22, 2026
4 checks passed
@thinmintdev thinmintdev deleted the fix/releases-proxy-observability-2026-05-22 branch May 22, 2026 05:04
thinmintdev added a commit that referenced this pull request May 22, 2026
…OKEN (#15)

PR #14's observability change confirmed the failure mode:

  $ curl -sSI https://releases.hal0.dev/stable.json | grep x-hal0
  x-hal0-proxy-failed: gh-list-403

The very first upstream call — the releases list on api.github.com —
gets a 403 from inside CF Pages Functions. Cloudflare's outbound IP
pool is shared across every CF customer, so the anonymous 60/hr/IP
limit is permanently exhausted long before our request arrives.
Authenticated requests get 5000/hr per token, comfortably above any
realistic load on releases.hal0.dev.

Changes:
  - New `authHeaders(token)` helper attaches `Authorization: Bearer`
    when env.GITHUB_TOKEN is present; falls back to anonymous when
    not set (so this PR is a no-op until the secret lands in CF).
  - Both fetches (releases list + asset download) use the helper.
  - `PagesFunctionContext` type now includes `env: { GITHUB_TOKEN? }`
    and the proxy function takes the token as a parameter.
  - `gh-list-<status>` reason gets a `-auth` / `-anon` suffix so the
    `x-hal0-proxy-failed` header tells us whether the secret is wired.

The token only needs public-repo read scope — a fine-grained PAT
restricted to Hal0ai/hal0 with `Contents: Read` is the minimum.
Classic tokens with no scopes also work (they get 5000/hr for
public-repo reads).

Pre-merge requirement: set `GITHUB_TOKEN` in CF Pages env (production
+ preview) before merging, or the fix is a no-op and stable.json
keeps serving the placeholder.

Co-authored-by: Claude Opus 4.7 (1M context) <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.

1 participant