Verbatim symptom
banned based on your browser's signature# The Default That Never Got Used
A default value that nobody ever exercises is a bit like a fire exit that's been painted shut. It looks fine on the plan. Then you actually push on it.
This is a post-mortem of one of those. It lives in the claude-content repository, it was introduced on 2026-06-06, and it was found and fixed on 2026-08-03. The commit that fixed it is d75ec75. The short version: a base URL default pointed at the wrong host, the wrong host rejected every programmatic client, and for the best part of two months nothing noticed because nothing ever used it.
Symptom
The symptom was blunt. A new generation script I was writing used the documented default for SCRIPTGRAIN_BASE_URL, made five API calls, and all five failed. Five out of five. No partial success, no flaky one in the middle.
The failures weren't the usual kind either. The response was a 403, and the body wasn't JSON. It was an error page carrying Cloudflare's error 1010, with the line that gave the whole thing away: "banned based on your browser's signature".
Now, if you've never seen that string, it reads like you've personally been banned from something. You haven't. Your HTTP client just doesn't look like a browser, and something sitting in front of the server has decided that's suspicious. I mean, fair enough, it isn't a browser. It's a script.
The non-JSON body mattered more than I first gave it credit for. Our client expected JSON, so what surfaced wasn't a tidy "forbidden" but a failed parse on top of the 403. Two errors for the price of one, and the second one is the more confusing of the two.
What we thought it was
My first read was credentials. A 403 on every call looks exactly like a bad or missing key, and that was believable because it's the most common reason for one. So I went looking at the auth header, then at the key itself, then at whether the key had been rotated.
None of that was it, which is the annoying part of a wrong diagnosis: it takes real time to disprove, because each check is reasonable on its own.
The second guess was rate limiting or some kind of block on my network. Also believable. Error 1010 literally talks about being banned, so of course I wondered whether I'd been banned. But a ban on me wouldn't explain why the same repo had been working fine for weeks.
And that was the clue I should've leaned on sooner. The repo worked. Something else was working. Just not this script, and not through the default.
Root cause
The actual cause was boring, which is usually how it goes. In src/config.ts at line 48 (as it stood before the fix, so src/config.ts:48@d75ec75^), the content agent defaulted SCRIPTGRAIN_BASE_URL to the ScriptGrain marketing domain's functions path.
That's the wrong place. The marketing domain isn't the API host. It sits behind Cloudflare, and Cloudflare answers programmatic clients on that domain with the 403 and the 1010 page. So any call that went through the default went to a host that was never going to talk to it.
The reason this was invisible comes down to .env. The repo's own .env always set SCRIPTGRAIN_BASE_URL explicitly, which meant the default in config.ts was never reached in normal running. The wrong value existed. It just never fired.
And then the second half, which is what made it properly sticky. .env.example, at line 22 (.env.example:22@d75ec75^), carried the same wrong value, written out explicitly. So the documented default and the coded default agreed with each other, and both were wrong. Anyone following the example file would've copied the broken URL straight into their own environment.
That agreement is the sneaky bit. Two places saying the same thing feels like confirmation. It isn't. It's one mistake written twice.
The fix
The fix is in d75ec75, and it changes where the default comes from. Rather than hard-coding a host, the default now derives from a separate Supabase URL variable and resolves to the real API host. You can see the new behaviour at src/config.ts:50@d75ec75.
The commit records the before and after, which is the part I like. The old default returned 403. The new one returns 200 with 8 profiles. That's a proper check, not a vibe. Same request shape, different host, completely different answer.
I'd rather derive a value than restate it, and this is why. If the API host is already implied by another variable the project has to set anyway, a second hand-typed copy of it is just a second place to be wrong.
Why it happened
So why did it survive for so long? Not because anyone was careless. It survived because every individual thing looked fine.
The code compiled. The default was a plausible-looking URL on a domain with the right name on it. Review saw a string that resembled the real thing, and a string that resembles the real thing is very hard to flag, you know? And the .env override meant the tests and the day-to-day runs never touched it.
That's the systemic cause: the only environment that ran the code was the one environment configured to hide the bug. A default that's always overridden is untested by construction. It sits there, looking authoritative, and nobody has a reason to doubt it until a new script comes along that trusts the documentation.
Which is exactly what happened. The first thing to rely on the default was the first thing to find out it was broken.
How to avoid it
A few things that carry over to a codebase that isn't ours.
Run something with your overrides stripped out. Not as a ceremony, just once, in CI or a throwaway shell: start from the example file and the code defaults only, and make one real call. If your defaults are never exercised, that's the only way you'll ever learn what they do.
Treat your example env file as code, because it is. It's the thing new people copy from. If it's wrong, it's wrong for everyone who trusts it, so it deserves the same check as anything else.
Be suspicious of agreement. When the example file and the code default match, that tells you they were written together, not that they're right. Check the value against the thing it points at.
And derive where you can. If one variable already tells you where the service lives, don't hand-type a second copy.
Anyway, that's the lot. I had a URL that looked right and a host that wasn't, plus a 403 that was only ever telling me one thing: the default was wrong, I just never pushed on the door.
Every commit, file reference and date above is checked
against this incident's Noctem evidence pack before publication. Evidence:
claude-content-scriptgrain-base-url-default-banned-host.