Skip to content

fix(i18n): define the two missing oauth error strings, and gate the rest in CI - #223

Merged
ABB65 merged 1 commit into
mainfrom
fix/oauth-revoke-error-strings
Sep 2, 2026
Merged

fix(i18n): define the two missing oauth error strings, and gate the rest in CI#223
ABB65 merged 1 commit into
mainfrom
fix/oauth-revoke-error-strings

Conversation

@ABB65

@ABB65 ABB65 commented Sep 1, 2026

Copy link
Copy Markdown
Member

Follow-up to the note at the end of #219.

The bug

errorMessage() returns the key itself when the dictionary has no entry:

const value = dictionary('error-messages').locale(locale).get(key) ?? key

Two keys thrown by DELETE /api/workspaces/:workspaceId/connected-apps/:grantId were never defined, so the endpoint has been answering with the literal strings oauth.grant_not_found and oauth.revoke_forbidden as its error message.

Scope correction from my note on #219: I said these show to users as raw keys. In the connected-apps panel they do not — handleRevoke catches and shows its own connected_apps.revoke_error toast, discarding the server message. The keys surface in the HTTP error body for direct API callers and in error reporting, not in that UI. Worth fixing, smaller blast radius than I implied.

The fix

Two dictionary entries, matching the tone of the neighbouring oauth.* strings:

  • oauth.grant_not_found — "Connected app not found. It may already have been revoked."
  • oauth.revoke_forbidden — "You can only revoke apps you connected yourself. Ask a workspace owner or admin to revoke this one."

Why it went unnoticed, and why that part matters more

A missing key fails nothing. Not lint, not typecheck, not any test — the fallback turns a missing string into a silent degradation instead of an error. There is no signal until someone reads an error body and sees a dotted identifier.

pnpm check:strings (scripts/check-dictionary-keys.mjs) has existed the whole time and catches exactly this. It was simply never wired into CI. It now runs next to Lint in the ci job, so the next missing key fails the build.

The script already separates dynamic call sites (t(\content.status_${'$'}{value}`)` and friends) into a manual-review list that does not fail, so the 16 existing dynamic usages stay non-blocking. It exits non-zero only on literal keys with no entry — currently none.

Verification

  • pnpm check:strings✓ All literal dictionary keys are defined.
  • 1231 unit + integration tests pass after regenerating the client.

…est in CI

`errorMessage()` falls back to returning the key when the dictionary has no
entry, so `oauth.grant_not_found` and `oauth.revoke_forbidden` have been
shipping as their own key names in the revoke endpoint's HTTP error body ever
since the connected-apps panel landed. That panel swallows the server message
behind a generic toast, so this never showed in its UI — it surfaces to direct
API callers and in error reporting.

Nothing caught it: a missing key fails no lint rule, no type, and no test,
because the fallback makes it a silent degradation rather than an error.
`check:strings` has existed the whole time and was simply never wired up. It
now runs in CI next to lint, so the next one fails the build instead of
shipping.
@ABB65
ABB65 merged commit 340202e into main Sep 2, 2026
2 checks passed
@ABB65
ABB65 deleted the fix/oauth-revoke-error-strings branch September 2, 2026 09:07
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.

1 participant