Skip to content

Say what a refused request means, instead of its status code - #159

Merged
patchstackdave merged 1 commit into
mainfrom
cli-auth-refusal-messages
Aug 20, 2026
Merged

Say what a refused request means, instead of its status code#159
patchstackdave merged 1 commit into
mainfrom
cli-auth-refusal-messages

Conversation

@patchstackdave

Copy link
Copy Markdown
Contributor

ENG-3644

Every per-site Pulse endpoint requires a credential, which makes a refusal the likeliest failure a misconfigured project meets. None of the three write paths handled one — they fell through to:

Patchstack returned 401.

That names neither the cause nor the fix, and this output is routinely read by an AI coding agent that has no other source for the answer.

Three causes, three remedies

situation what it means what to do
401, no credential held a setup step never ran login, or PATCHSTACK_API_KEY
401, credential held expired, revoked, or the site is gone reissue it
403 valid credential, wrong site fix siteUuid in .patchstackrc.json

The last one was previously indistinguishable from the first, and it is the one that would otherwise send someone hunting a network problem.

A regression this also fixes

postPackageRemoved read gone off a 404. A deleted site can no longer answer 404 there: the route resolves the site from the credential, so once the site record is gone the credential resolves to nothing and the answer is 401. Verified directly against the server rather than assumed.

Unhandled, uninstalling a site that was already deleted in the dashboard would advise re-running login for a site that does not exist. So the question goes to the endpoint that can still answer it — public widget settings, which needs no credential and 404s for a removed site — and only then reports gone.

Tests

tests/auth-failure-message.test.ts, 9 cases, three of which are controls:

  • the helper returns null for every non-auth status — without it, a 500 or a 422 would be relabelled an auth problem, which is worse than the status code it replaced
  • a genuine server error still reports as one, proving the new branch did not swallow the rest
  • 401 on package-removed is only gone when the site really is gone — otherwise a real credential problem would read as a removed site and be silently ignored

Mutation-checked: collapsing the two 401 messages fails the two that distinguish them; answering for every status fails both non-auth controls; treating any 401 as gone fails only the control that says otherwise.

Full suite green (1258 passed, 6 skipped), typecheck and template typecheck clean.

Not touched

fetchSiteStatus hits the public widget-settings endpoint and is correctly unauthenticated. The runtime's rules and detections paths already attach a credential.

Every per-site endpoint now requires a credential, which makes a refusal the
likeliest failure a misconfigured project meets. Until now none of the three
write paths handled one: they fell through to "Patchstack returned 401", which
names neither the cause nor the fix. That output is routinely read by an AI
coding agent with nowhere else to look it up.

There are three causes and three different remedies. Holding no credential is a
setup step that never ran. Holding one that is refused means expired, revoked, or
a site that no longer exists. A 403 means the credential is valid and simply
belongs to another site — usually a config file carrying the wrong UUID — which
was previously indistinguishable from having no credential at all.

Also fixes a regression the boundary change introduces. The package-removed
signal read "gone" off a 404, but a deleted site cannot answer 404 there any
more: that route resolves the site from the credential, so once the site record
is gone the credential resolves to nothing and the answer is 401. Unhandled, this
would advise re-running login for a site that no longer exists. The question goes
instead to the endpoint that can still answer it — public widget settings, which
needs no credential and 404s for a removed site.

One shared helper so the three paths cannot drift apart, and it returns null for
anything that is not an authentication failure: relabelling a 500 as an auth
problem would be worse than the bare status code it replaced. Both of those are
covered by controls, along with the difference between the two 401 messages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderbuds

coderbuds Bot commented Aug 20, 2026

Copy link
Copy Markdown

Improves auth error messages with clear, actionable instructions.

🎯 Quality: 100% Elite · 📦 Size: Medium

📈 This month: Your 107th PR — above team average · Averaging Excellent

See how your team is trending →

@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

@patchstackdave
patchstackdave merged commit 92e40f6 into main Aug 20, 2026
6 checks passed
@patchstackdave
patchstackdave deleted the cli-auth-refusal-messages branch August 20, 2026 14:06
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.

2 participants