Skip to content

ci, web: enumerate the account, reap leaked previews, and fix three 301s onto a 404 - #375

Merged
JulienMartel merged 2 commits into
mainfrom
worktree-peppy-meandering-dragonfly
Aug 15, 2026
Merged

ci, web: enumerate the account, reap leaked previews, and fix three 301s onto a 404#375
JulienMartel merged 2 commits into
mainfrom
worktree-peppy-meandering-dragonfly

Conversation

@JulienMartel

Copy link
Copy Markdown
Contributor

What

Two things, both found by taking the one open item §5.3 left — "a look at the Cloudflare dashboard's Workers ▸ Routes for both zones".

  1. .github/workflows/preview-sweep.yml — a daily (+ workflow_dispatch, with dry_run) sweep that lists the account's Workers, asks GitHub whether each nebelhaus-pr-<n>'s PR is still open, and deletes the ones that aren't. hausfold.co gets the same file in its own repo.
  2. web/worker.js — three published nebelhaus.com URLs have been 301'ing onto a 404 since rename: drop nebelhaus — decision 10, §11, and the workshop half #372 deployed at 10:06Z today. Fixed, plus two tests that would have caught it.

Also: README.md and AGENTS.md picked up "new installs scaffold haus" in #372. They don't — haus's bootstrap.sh:567 writes inputs.nebelhaus.url for a fresh install.

Why

The look didn't need the dashboard, and it wasn't clean. The wrangler OAuth token on the machine carries account (read) + workers_scripts, so two REST calls enumerate everything — a check something can run, not a thing to remember. It found:

nebelhaus-init ✅ confirmed gone
nebelhaus.com/*nebelhaus ✅ the map, as declared
api.hausfold.conebelhaus 🚨 a route in no config in either repo; dormant only because that subdomain has no DNS record. 👤 — no merge removes it
4 leaked PR previews 🚨 nebelhaus-pr-321/341, hausfold-pr-16/22 — all public

🚨 The two nebelhaus-pr-* re-opened the hole §5.3 closed. They predate the 301 map, so they are copies of the old site Worker: …/init.sh → 200 with bootstrap.sh, and ?ref=<a real 40-hex sha> → 200 with the bootstrap from that ref. Deleting nebelhaus-init closed the path it sat on, not the code — the code lives on in every preview upload of that era, each with its own hostname.

preview-web.yml's cleanup job isn't at fault; all three leak modes are its trigger failing, which no pull_request job can fix: the paths: filter is judged on the PR's whole diff (321 touched web/** early, merged one notes/ file); a PR closed in the same operation that deletes its head branch gets no run at all (341); and a closed PR never re-fires, so one transient delete failure leaks forever.

The 404s are a narrower lesson: when a rename moves a page, curl the destination — don't derive it by substituting the new name into the old path. /desktops/hacker/ looked right and never existed.

Verify

# the workflow, before merge
shellcheck <the extracted run body>          # clean
# and driven against the live account in dry-run:
#   would delete nebelhaus-pr-321 (PR #321 is closed)
#   would delete nebelhaus-pr-341 (PR #341 is closed)
# — those two and nothing else

cd web && npm ci && npm test                 # 17 pass (15 before)

# after merge, to actually clear them (two runs — each repo sweeps its own prefix)
gh workflow run preview-sweep.yml -R hausfold/workshop
gh workflow run preview-sweep.yml -R hausfold/hausfold.co

# and the 301s, after deploy-web.yml runs
curl -sIL https://nebelhaus.com/start/first-run   # → 200, was 404

Watch out

  • ⚠️ Each sweep copy only reaps its own prefix — deliberately: both repos' Workers share one Cloudflare account and the name match is anchored at both ends, so neither production Worker (nebelhaus, hausfold) nor the sibling's previews can be caught. Clearing all four is therefore two dispatches.
  • ⚠️ The list call's scope is inference until the first run. The CI token is provisioned Workers Scripts:Edit; Read comes with Edit on Cloudflare's table, but nothing here has ever exercised a list. The job fails loudly and names the scope if not.
  • 👤 api.hausfold.co is account-side and in no config — see §5.3's box for the delete.
  • The sweep asserts result_info.total_count against what it got and refuses to run on a truncated page, rather than paging speculatively — a silently truncated list is the same "reports clean forever" failure this whole section is about.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PkTBtapKcZTCeTqfhRZEJ7

…rving the ?ref= hole

§5.3 closed with a *look* rather than a step: the Cloudflare dashboard's
Workers ▸ Routes for both zones, "since nothing in either repo enumerates what
is actually deployed". Taking it turned up two things.

It never needed the dashboard. The wrangler OAuth token already on the machine
carries account:read and workers_scripts, so two REST calls list every Worker
and every zone route — a check something can run rather than a thing to
remember.

And the sheet wasn't clean. Four PR-preview Workers leaked from PRs closed
2026-08-11/12, all of them workers_dev=true and publicly reachable. Two predate
the 301 map, so they are copies of the old site Worker: /init.sh answers 200,
and ?ref=<a real 40-hex sha> answers 200 with the bootstrap from that ref —
the fork-network hole §5.3 recorded as closed. Deleting nebelhaus-init closed
the path it was on, not the code; the code lives on in every preview upload of
that era, each with its own hostname.

preview-web.yml's cleanup job is not at fault — both leaks are its trigger
failing to fire, which no pull_request-triggered job can fix. The paths filter
is evaluated against the PR's whole diff, so a PR that touched web/** early and
not finally deploys a preview and never fires `closed` (nebelhaus-pr-321). And
a PR closed in the same operation that deletes its head branch gets no `closed`
run at all (nebelhaus-pr-341).

So ask the other question, from state: preview-sweep.yml lists the account's
Workers daily, asks GitHub whether each <prefix>-pr-<n>'s PR is still open, and
deletes the ones that aren't. Anchored name match, so neither production Worker
can match; workflow_dispatch takes a dry_run. Verified by driving the extracted
job body against the live account in dry-run — it named those four and nothing
else — and shellcheck-clean.

One thing no merge can fix, left 👤 in §5.3: an api.hausfold.co route pointing
at the nebelhaus.com 301 map, declared in no config in either repo, dormant
only because that subdomain has no DNS record.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkTBtapKcZTCeTqfhRZEJ7
@JulienMartel
JulienMartel temporarily deployed to nebelhaus.com / PR #375 August 15, 2026 11:05 — with GitHub Actions Inactive
… the code

Three published nebelhaus.com URLs have been redirecting onto a 404 since
#372 deployed at 10:06Z: /start/what-is-nebelhaus, /start/first-run and
/reference/keybindings all pointed at hausfold.co/desktops/hacker/, which is
the desktop's old path with its new name substituted in. hausfold.co had
retired that tree into the docs hours earlier (hausfold.co#47) — /desktops/
hacker/ is a 404, /desktops/nebelhaus/ is itself a 301 into
/docs/haus/desktops/hacker/, and the #keys fragment the keybindings row
carried does not exist on any page any more (the ids are what-it-turns-on,
first-moves, making-it-yours, where-to-go-next).

So: the two desktop rows land on /docs/haus/desktops/hacker/, keeping the
#first-moves fragment which is real there. Keybindings follows the ruling
hausfold.co already made for the same URL — the keys were a shortened reprint
of the windows room and the port deliberately didn't copy them, so it lands on
the page that has them rather than on a fragment of the desktop's sheet.

The suite stayed green throughout, which is the part worth keeping: it proves
the map is complete and points at hausfold.co, and nothing in it fetches, so a
destination that 404s is invisible to it. Two tests now pin the renamed rows by
hand, and one fails any future row pointing under hausfold.co/desktops/ — that
prefix is either a 404 or the second hop this file exists to avoid.

Separately, README.md and AGENTS.md picked up "new installs scaffold `haus`" in
#372. They don't — haus's bootstrap.sh:567 writes inputs.nebelhaus.url for a
fresh install, so a new machine spells it exactly as this one does. That claim
is the §11.2 trap dressed as progress: believing the input has half-moved is
what leads to finishing it by editing bench's OVERRIDABLE alone, which is the
edit that makes every --override-input silently no-op while reporting success.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkTBtapKcZTCeTqfhRZEJ7
@JulienMartel
JulienMartel force-pushed the worktree-peppy-meandering-dragonfly branch from 68605e9 to af1b43b Compare August 15, 2026 11:06
@JulienMartel
JulienMartel merged commit 53fd9e0 into main Aug 15, 2026
6 checks passed
@JulienMartel
JulienMartel deleted the worktree-peppy-meandering-dragonfly branch August 15, 2026 11:14
JulienMartel added a commit that referenced this pull request Aug 15, 2026
…377)

Both preview-sweep copies merged and ran 2026-08-15 11:16 UTC, dry-run first
then for real. All four leaked Workers deleted; re-enumerating the account
leaves nebelhaus, hausfold and one open PR's preview, and the two hostnames
that were serving /init.sh?ref=<sha> now 404 — checked against the account and
the URLs, not read off the job log.

Three things the live run settled that construction alone could not. The keep
arm is real: hausfold.co's run had hausfold-pr-49 (an open PR) in front of it
and left it alone in both passes, which is the safety property that most needed
demonstrating before anyone trusts this on a cron. The CI token can list — the
header called Workers Scripts:Read "inference until the first run", and it
isn't any more. And preview-web.yml's fast path is fine when it fires: #375's
own preview was already gone before the sweep looked, which is the difference
between a backstop and a replacement.

Production was untouched: hausfold.co 200, nebelhaus.com/guides/pounce still
301s onto the launcher room.


Claude-Session: https://claude.ai/code/session_01PkTBtapKcZTCeTqfhRZEJ7

Co-authored-by: Claude Opus 5 (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