The error that was only there when I was

A cream roof rat peeking out from inside a fabric hammock, visible only from one angle -- part 11 of Notes from building this site

By · 7 min read · Last saved

This article comes with a free Claude skill

The rule this story ends with is packaged as wp-admin-script-leak. It is one small file you drop into Claude Code so your own assistant works this way too. It is public domain: copy it, change it, no attribution and no permission needed. How to install it is at the end of this article.

I had the browser console open on one of this site’s public pages. That habit came from an earlier mistake, and it paid off again: a red line of text, a TypeError, something about not being able to read a property of undefined, coming from a script I did not recognise.

I asked the assistant to look. It fetched the page, checked that it loaded, checked the markup, and told me the page was clean. No error. I looked again in my own browser and the error was still there, plain as anything.

The thing that cracked it was an accident. I opened the same page in a private window to check how it looked to a stranger, and the console was empty. Logged in: error. Logged out: clean. The page was fine for everyone in the world except the one person building it.

A JavaScript error only logged-in users can see

That pattern, an error that vanishes when you log out, is not a fluke and not “something odd in my browser”. It is a specific, known class of bug, and the asymmetry is its signature.

Here is what had happened. A script meant for the administration screens was being loaded on the public pages too, but only for logged-in users, because that is who gets sent the extra scripts. Admin scripts are written assuming they are on an admin screen. They expect certain global variables, certain page structure, certain other scripts already loaded. On a public page none of that exists, so the script falls over the moment it starts.

And the reason this class of bug survives in the wild is exactly the asymmetry. The person most likely to see the error is the developer, who is always logged in. The person least likely to see it is a visitor, who never is. So the one witness dismisses it as local weirdness, and no one ever files it as a bug. It hides behind the person best placed to catch it.

The assistant’s check missed it for the same reason. Fetching the page as an anonymous request is a logged-out visit. The offending script was never even sent, so there was nothing to find. The check was honest and useless at the same time.

A script built for one room was loading in every room

The mechanics, for anyone technical who has not met this corner of WordPress: you do not put scripts on pages directly, you register them from a hook, and the hook you choose decides where they load. There is one hook that fires only on admin screens, admin_enqueue_scripts, and one that fires only on the public front end, wp_enqueue_scripts.

Our mistake was a third option. The assistant had put the enqueue in a shared setup function attached to a hook that fires everywhere. That is the usual cause of this bug: not a wrong decision about where the script belongs, but no decision at all. The script went wherever the hook went, and the hook went everywhere.

It is worth saying that this is not always your own code. Plugins do the same thing on your behalf, so before assuming you wrote the bug, check whether you installed it.

The fix was small and boring, which is how you know it is the right one. Move the enqueue to the hook that matches the context. Admin scripts under admin_enqueue_scripts, front-end scripts under wp_enqueue_scripts, gated further by page or template if needed. The hook itself does the sorting, and no code at runtime has to be clever about it.

The guard that could never fire

There is a tempting non-fix here, and the assistant reached for it first: keep the shared hook, but wrap the enqueue in a check on is_admin().

Inside wp_enqueue_scripts that check is dead code. That hook does not fire on admin screens at all, so the condition can never be true. It reads like protection when you review the code, and it protects nothing. A guard that cannot fire is worse than no guard, because it makes the code look considered.

There is a second trap folded inside the first. is_admin() does not mean “the current user is an administrator”. It means “an admin screen is being rendered right now”. Those sound alike and are entirely different questions, and confusing them is its own bug, one with security consequences, because code that thinks it is checking who you are is actually checking where you are standing.

Why the page half-worked instead of breaking

One more thing worth knowing when you read an error like this. An uncaught exception kills the rest of that one script block. The other scripts on the page still run. So the page does not visibly break. It loads, it renders, most things work.

The damage is indirect. The crashed script never finished setting itself up, so anything that depended on it later simply does nothing, with no error at the moment you notice. If that sounds familiar, it is because our very first article in this series was about a button that did nothing. Silent half-failure is what this kind of crash looks like from the outside.

How we check for it now

The verification rule is the part I most want to pass on, because it is the part every standard check gets wrong.

To see this bug, you must load a front-end page in a real browser, as a logged-in user with elevated capabilities, and confirm the console is clean. That is the whole test. A logged-out check will pass and prove nothing, because the script that would crash is not sent to logged-out visitors. Fetching the page and checking the status code proves nothing. Checking the markup proves nothing. The bug lives only in the one place those checks never look: a privileged user’s browser session.

So we do that now, every time scripts are added or moved. Once logged in, once logged out, console open both times. It takes a minute. And when the assistant reports that a page is fine, I ask the question this incident taught me: fine for whom?

The rule

  • Enqueue every script from the hook that matches its context, admin or front end, never from a hook that fires everywhere.
  • Never guard wp_enqueue_scripts with is_admin(); the check can never be true and the guard is dead code.
  • Remember that is_admin() asks which screen is rendering, not who the user is.
  • Verify front-end pages in a real browser session as a logged-in privileged user, and assert the console is clean.
  • Treat any error that disappears when you log out as a bug, not as browser weirdness.

Get the skill

Everything above is generic. None of it is about rats, and none of it is specific to this site. So the rule is also published on its own as a Claude skill: a single Markdown file that an AI coding assistant reads and applies when the situation comes up.

The file: wp-admin-script-leak/SKILL.md

Or take the whole set:

git clone https://github.com/blonderoofrat/agent-skills

Installing it in Claude Code. Copy the skill’s folder into one of these, so the file ends up at .../skills/wp-admin-script-leak/SKILL.md:

  • ~/.claude/skills/ (available in every project on your machine)
  • your-project/.claude/skills/ (that one project only)

Claude reads it at the start of the next session and applies it when what you are doing matches the description at the top of the file. You can also ask for it by name.

Using a different assistant? The file is plain Markdown with a two-line header. Paste the body into whatever system prompt, rules file or instructions file your tool uses. Nothing in the rule itself is Claude-specific.

Licence: CC0, public domain. Copy it, adapt it, ship it in commercial work, no attribution required. These are deliberately frozen snapshots rather than a maintained project, so if one is wrong for your situation, change it. That is easier than asking us to.


Part of Notes from building this site: articles about working practices that exist because something here went wrong first. The rule above is also published on its own, as a free public-domain instruction file for AI coding assistants, the wp-admin-script-leak skill, in blonderoofrat/agent-skills on GitHub.

Filed under News about this site