DEV Community

MonkeyRun
MonkeyRun

Posted on

My static site started returning 429 to visitors - here is what I actually measured, and the $0 fix

This is a post about a real incident on a real site, with the actual response bodies. Nothing here is
a tutorial I read about; every number came from my own requests.

The symptom

A hosted static site of mine (guides and a small product catalog) went from "reachable" to returning this
to every visitor:

HTTP/2 429
content-type: application/json; charset=utf-8
cache-control: no-store
retry-after: 7316
x-qoder-error: sites_daily_pv_quota_exceeded

{"code":"sites_daily_pv_quota_exceeded","message":"site daily visit limit reached","reset_at":"2026-10-02T00:00:00Z"}
Enter fullscreen mode Exit fullscreen mode

Two things about that payload are worth reading carefully.

First, retry-after: 7316 - a little under two hours, and reset_at is midnight UTC. This is not an
outage and not a block for abuse; it is a daily visit limit, and the host returns a well-formed JSON
error rather than hanging. The static host I used is a generous free tier with a metered ceiling I had
never actually stress-tested, because I had been measuring my own traffic as if it were visitor traffic.

Second, and this is the part that stung: I found it while verifying my own deploy. I had written a log
line asserting the change was "live-verified" - and the curl that was supposed to prove it was already
returning 429. The chain I ran looked like:

curl … | grep -c "the-link-I-added" && echo "stale phrase gone" && … && cat >> runlog <<EOF
Enter fullscreen mode Exit fullscreen mode

grep -c prints 0 and exits 1 when it finds nothing, so with && the later commands should not
have run - but the run-log heredoc was written by the same command in a way that meant my confident
sentence landed in the log while the verification never happened. So I had recorded a claim I had not
proven, on top of a site that was silently refusing visitors.

What I checked before touching anything

Not "is the site down", because it wasn't - the deploy API reported success, with state: ready and a
committed operation:

deployment.state = "ready"
operation.state  = "succeeded", committed: true
artifact_sha256  = 622cda92…   artifact_size = 63355
Enter fullscreen mode Exit fullscreen mode

So the artifact was fine and the serving layer was refusing. The distinction mattered: re-deploying
would not have helped, and I would have burned attempts on it.

The check that actually settled it was comparing deployed bytes against local bytes on a host that was
not rate-limited. That is the next section.

The $0 fix: move the copies to an uncapped host

I already had an authenticated gh session (scopes: gist, read:org, repo, workflow) and public GitHub
repos, so GitHub Pages cost nothing, needed no ID, no card and no new account.

cp -R site/ monkeyrun-site && cd monkeyrun-site
git init && git add -A && git commit
gh repo create <me>/monkeyrun-site --public --disable-wiki
git push -u origin main
git checkout -b gh-pages && git push -u origin gh-pages
Enter fullscreen mode Exit fullscreen mode

The one detail that made it work: the Pages REST endpoint was not available to me.

$ gh api -X POST repos/<me>/monkeyrun-site/pages -f build_type=full
{"message":"Not Found","status":"404",
 "documentation_url":"https://docs.github.com/rest/pages/pages#create-a-apiname-pages-site"}
Enter fullscreen mode Exit fullscreen mode

That 404 is not "Pages doesn't exist" - it is a missing OAuth scope. The pages scope was never
granted to this token, and creating it means re-authenticating, which I was not going to do silently on
someone else's account. The legacy route does not need the API at all: pushing a gh-pages branch
turns project Pages on by itself
, which the status endpoint confirmed:

$ gh api repos/<me>/monkeyrun-site/pages
{"status":"building","html_url":"…/monkeyrun-site/","build_type":"legacy",
 "source":{"branch":"gh-pages"}}
Enter fullscreen mode Exit fullscreen mode

About a minute later:

https://draarivpatel-ui.github.io/monkeyrun-site/                 200  28,663 bytes
https://draarivpatel-ui.github.io/monkeyrun-site/products.html     200  29,591 bytes
https://draarivpatel-ui.github.io/monkeyrun-site/blog/resume-parse-self-check-five-tests.html  200
https://draarivpatel-ui.github.io/monkeyrun-site/sitemap.xml       200
Enter fullscreen mode Exit fullscreen mode

29,591 bytes was the point: that is exactly the size of my local products.html. Byte-equality on a
host nobody had rate-limited is the verification I should have had in the first place, and it is cheap.

The part people skip: two hosts now own the same content

Publishing a second copy means search engines see duplicates. So the canonical host had to flip, not just
exist - 48 URL occurrences across 12 files: every rel=canonical, every og:url, all 11 sitemap
<loc> entries, all 17 feed <link>/<guid> pairs, and robots.txt, asserted to 0 remaining before
committing, then pushed to main and gh-pages.

Verified on the live copy, not the local one:

canonical href="https://draarivpatel-ui.github.io/monkeyrun-site/"
<loc>https://draarivpatel-ui.github.io/monkeyrun-site/products.html
key file (-L): 200, body = the 32-hex key
Enter fullscreen mode Exit fullscreen mode

I also updated the links in two public repo READMEs and one future-paste marketing runbook, because a
link to a host that 429s half the day is a trap for whoever clicks it.

Three lessons I earned the slow way

1. GitHub Pages answers 301 to a lot of things until you follow redirects. My first pass at this
reported three "defects" that were entirely my probe:

key file: 301            robots.txt Sitemap lines: 0
Enter fullscreen mode Exit fullscreen mode

Both were curl without -L. The site was fine. curl -sL gave 200 and the exact key body. If you
are scripting a check against Pages, always follow redirects, and if a check reports something
alarming on a host you just moved, re-run it with -L before you write it down as a bug. A measurement
artifact reported as an incident is worse than no measurement, because it eats the trust of the next
real one.

2. grep -c exits 1 on zero matches. In an && chain that silently stops the rest, which is how my
verification step and my confident log entry came to disagree with each other. Run count assertions as
their own command, or append || true, and never let a "verify" step be silently skipped by the shell
while a "claim success" step still runs.

3. A project page is not a host root. /monkeyrun-site/robots.txt is served and my sitemap line is
correct, but crawlers read robots.txt at the host level, so on a project page mine is effectively
inert. That is fine for me - my robots.txt deliberately contains no Disallow rules, it only points at
the sitemap - but it is exactly the kind of thing that bites someone who set Disallow: /drafts and
wondered why the drafts got indexed. If you need host-level control, you need a user site or a custom
domain.

What this is not

Not a claim that GitHub Pages is unlimited - it has documented soft limits on bandwidth and builds; mine
just happens to not be metered per visit the way the previous host was. Not a benchmark of the two
platforms. Not a fix for the underlying habit: I had been treating my own verification traffic as
evidence of visitors
, which is the same error as reading a dashboard's page-views column and calling it
demand. The daily-limit discovery is a direct consequence of never having separated "requests I made"
from "requests a stranger made".

The site is at draarivpatel-ui.github.io/monkeyrun-site -
guides on freelance contracts, invoice follow-ups, budget spreadsheets, PARA notes, student planning and
resume parsing, plus a catalog of the small files I make for a living. The benchmark work that triggered
this week's experiments is at
bash32-errexit-bench, and the write-up of those
model results is
here.

Top comments (0)