Skip to main content
← Writing
EngineeringAug 30, 20266 min read

A 500 That Only the Owner Could See

The release page for mcp-scan v2.0.8 threw a raw 500. Anyone could open it and see a clean page. I opened it, signed in as the repo owner, and got a stack trace instead. Same URL, same server, two different outcomes.

Abanoub Rodolf Boctor

The symptom

I’d tagged v2.0.8 of mcp-scan and pushed the release. Logged out, in a private window, the release page rendered fine: title, notes, assets, nothing unusual. Logged in as the account that owns the repo, the same path returned a 500 with no body worth reading, just GitHub’s generic error page.

That split is the whole story. It meant the failure wasn’t in anything an anonymous visitor could trigger. It was in something that only renders for someone with write access to the repo, which narrows the search space by a lot if you notice it early. I didn’t notice it early. I spent the first hour chasing the release itself.

Wrong turn one: the orphaned draft

The repo had an old draft release sitting around from an earlier, abandoned tag. Dangling tag, no published notes, the kind of thing you forget to clean up. It looked like exactly the sort of orphaned row that would confuse a release-listing query, so I deleted it. The 500 didn’t move. Same trace, same page, still owner-only.

Wrong turn two: recreating the release

Next guess: something was wrong with the v2.0.8 release row itself, maybe a malformed asset reference or a tag mismatch GitHub’s renderer choked on. I deleted the release and recreated it from the same tag, clean. Still 500, still only when authenticated. Two fixes in, both plausible, both wrong, and both cost real time because each one looked like it should have worked.

That’s usually the tell that you’re fixing the wrong layer: the fix addresses something real and broken-looking, but the error doesn’t care. If the error persists unchanged across two structurally different fixes to the same object, the object isn’t the problem.

Reproducing it properly

I stopped guessing at the release data and reproduced the exact failure in the owner’s own authenticated session instead of inferring it from behavior. Loaded the page, watched what actually renders differently when GitHub knows you’re the repo owner versus when it doesn’t.

The difference is a banner. If GitHub thinks your repo could be a GitHub Action and you own it, the releases page renders a “Publish this Action to Marketplace” prompt above the release notes. Anonymous visitors never see it, because it’s not their action to publish. It’s conditional, server-rendered, and it was the thing 500ing. The release itself, the part I’d been rebuilding, was fine the whole time.

The actual cause

mcp-scan ships as a GitHub Action, declared in action.yml, with the bare name mcp-scan. GitHub Marketplace assigns each published action a slug derived from its name, and that slug is supposed to be unique. At some point, a Marketplace listing using that exact slug had existed and been delisted. Delisting doesn’t free the slug. It stays reserved, permanently, with no visible record anywhere in the repo, the Action, or the release that it was ever taken.

So the Marketplace-publish component, on every render, tried to check slug availability for mcp-scan, hit a reserved-but-invisible collision, and threw. Server-side. Every time. Only for the owner, because only the owner gets that component at all.

The fix was one line. I renamed the action in action.yml from mcp-scan to mcp-scan Action. The derived slug changed, the collision went away, the Marketplace-publish check succeeded, and the 500 was gone on the next load. Nothing about the release, the tag, or the draft had ever been broken.

Lessons

  • “Works anonymous, breaks authenticated” means a different render path, not a flaky error. It is a routing clue, not noise. Chase what only the authenticated view adds before touching anything both views share.
  • A 500 on page X can come from a component about feature Y embedded in it. The releases page failure had nothing to do with releases. It had to do with a Marketplace-publish banner that happens to live on that page for owners.
  • Invisible global namespaces fail with no useful error. A delisted Marketplace slug stays reserved forever, with no trace in the UI that tells you why a new name collides. If a resource claims a name in a shared namespace you don’t control, budget time for exactly this kind of silent conflict.

Work with ThynkQ

Need help shipping the real thing?

Start with the free discovery call if the scope is still fuzzy. If the problem is already clear, ThynkQ can usually tell you whether this should be an audit, a build, or an engineering retainer.

Book the free discovery call

Related reading