Skip to content

Classify disabled-API 403s as misconfigured instead of expired - #1402

Merged
RhysSullivan merged 6 commits into
mainfrom
google-disabled-api-e2e
Aug 28, 2026
Merged

Classify disabled-API 403s as misconfigured instead of expired#1402
RhysSullivan merged 6 commits into
mainfrom
google-disabled-api-e2e

Conversation

@RhysSullivan

@RhysSullivan RhysSullivan commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

A Google 403 with reason accessNotConfigured / SERVICE_DISABLED means the API is disabled in the OAuth client's project; the credential itself still works. Health checks previously folded every 401/403 into the expired status, so these connections showed an Expired badge and a reconnect prompt that could not fix the problem.

Probes now classify that case as a new misconfigured status. The row shows an amber "API disabled" badge with the provider's own remediation message (console link clickable), and Check now explains the disabled API instead of prescribing a reconnect. Unrecognized 403s still classify as expired.

e2e recording

Invariant set: every consumer of the HealthStatus union

HealthStatus goes from four literals to five. There is no exhaustive switch
on it anywhere in the tree, so the compiler does not catch a missed consumer on
its own. The full set was enumerated before the change; each site below is
either compile-enforced or was reviewed and given a deliberate answer for the
new state.

Compile-enforced (a missing entry fails the build) — all updated:

  • packages/react/src/lib/health-display.tsHEALTH_STATUS_LABEL,
    HEALTH_TEXT_CLASS, HEALTH_INDICATOR_COLOR, HEALTH_BADGE_VARIANT, and the
    HEALTH_SEVERITY ordering map. These five Record<HealthStatus, …> maps are
    the only exhaustiveness enforcement that exists, so they are the safety net a
    sixth literal would trip. misconfigured sorts above degraded and below
    expired, and shares degraded's amber rather than expired's destructive red.

Reviewed, no change required (single-literal comparisons that already fall to a
correct default):

  • packages/react/src/lib/use-connection-health.ts — revalidation keys off
    === "healthy"; anything else revalidates, which is right for the new state.
  • packages/react/src/lib/admin-users-display.tsconnectionHealthStatus is
    a passthrough with an ?? "unknown" default.
  • packages/react/src/pages/admin-users.tsx — renders only through the Record
    maps above.
  • packages/react/src/components/integration-health-summary.tsx — aggregates
    via worstHealthStatus and reads the Record maps.
  • packages/plugins/openapi/src/sdk/backing.ts — the === "healthy" gates on
    identity/sample extraction.

Reviewed and updated:

  • packages/react/src/components/accounts-section.tsxmisconfigured is
    deliberately kept OUT of needsHealthAttention (expired/degraded): it gets its
    own amber "API disabled" badge and its own link-rendered detail, because the
    remediation is a console visit, not a reconnect. The handleCheck toast chain
    gained an explicit misconfigured arm ahead of its catch-all.
  • packages/react/src/components/add-account-modal.tsx — the tone ternary names
    misconfigured so it does not fall through to the destructive colour.
  • packages/core/api/src/admin/api.ts — the AdminConnectionHealth.status doc
    comment listed only the four old states; it now names all five.
  • packages/plugins/openapi/src/sdk/backing.ts — the one call site switched from
    classifyHttpStatus to the body-aware classifyProbeResponse. This is what
    lets misconfigured be produced at all.

Deliberately NOT emitting the new status:

  • packages/plugins/graphql/src/sdk/plugin.ts (healthFromIntrospectionError)
    and packages/plugins/mcp/src/sdk/plugin.ts (mcpLivenessFailureStatus) keep
    folding 401/403 into expired. Neither carries the structured error envelope
    the carve-out reads, and the reason markers are Google-shaped, so a GraphQL or
    MCP endpoint cannot produce this verdict. Matching on message text instead
    would risk a false misconfigured, which would hide a genuinely dead
    credential — the failure this classification is explicitly designed to avoid.
    mcpLivenessFailureStatus already narrows its own return to
    "expired" | "degraded", so it is unaffected by the wider union.

Distinct from dead-grant classification: a disabled API is a health-probe
verdict about a working credential; a dead grant is a refresh-time verdict about
a credential that can no longer be renewed. That classification lives in
oauth-helpers.ts / executor.ts and references HealthStatus nowhere, so the
two do not collide.

A SERVICE_DISABLED / accessNotConfigured 403 means the API is disabled in
the OAuth client's project; the credential is fine. Probes now classify it
as a new 'misconfigured' status: amber 'API disabled' badge, the provider's
own remediation text (console link clickable) on the row, and no reconnect
prompt.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 70961c9 Commit Preview URL

Branch Preview URL
Aug 28 2026, 09:30 AM

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 70961c9 Aug 28 2026, 09:32 AM

@pkg-pr-new

pkg-pr-new Bot commented Jul 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1402

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1402

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1402

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1402

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1402

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1402

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1402

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1402

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1402

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1402

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1402

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1402

executor

npm i https://pkg.pr.new/executor@1402

commit: 70961c9

The remediation text is the payload - the enable-API console link was
clipped by the one-line truncate. The scenario now asserts the text wraps
un-clipped and the link sits inside the visible box.
@RhysSullivan
RhysSullivan marked this pull request as ready for review August 28, 2026 18:40
@RhysSullivan
RhysSullivan merged commit 742a144 into main Aug 28, 2026
44 checks passed
@RhysSullivan RhysSullivan mentioned this pull request Aug 28, 2026
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