Persist connection health verdicts on failure and always send ifStaleMs - #1777
Merged
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | 295359f | Aug 27 2026, 10:47 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | 295359f | Commit Preview URL Branch Preview URL |
Aug 27 2026, 10:45 PM |
Contributor
Cloudflare previewTorn down — the PR is closed. |
@executor-js/cli
@executor-js/config
@executor-js/execution
@executor-js/sdk
@executor-js/codemode-core
@executor-js/runtime-quickjs
@executor-js/plugin-file-secrets
@executor-js/plugin-graphql
@executor-js/plugin-keychain
@executor-js/plugin-mcp
@executor-js/plugin-onepassword
@executor-js/plugin-openapi
executor
commit: |
RhysSullivan
force-pushed
the
fix/connection-health-verdicts
branch
from
August 27, 2026 20:25
8b0e52d to
5ed8ba4
Compare
Sending a freshness window for a non-healthy verdict gated the probe against the verdict the previous probe had just persisted, so an expired or degraded connection could not turn green until the window elapsed. That broke the recovery-on-next-load contract the health-checks-ui, graphql-introspection-health and mcp-oauth-reconnect-health scenarios are built on. Restore the unconditional probe for non-healthy verdicts and keep the server-side change, which is what actually fixes the reported symptom: a refused refresh now folds into a persisted verdict instead of the failure channel, so it is answered rather than raised.
The exact-count assertion sampled refresh grants at one instant while probes were still in flight, so it read 3 where it expected 2. Assert the two invariants that do not race instead: grants never outnumber the health requests that caused them, and a settled page issues no further probes.
RhysSullivan
marked this pull request as ready for review
August 28, 2026 01:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a connection's authorization server stopped re-minting its credential, the connection health endpoint answered with a bare 500 instead of a verdict, and the client kept asking. Two halves:
connectionCheckHealthpersisted a verdict only on the success path, and a credential-resolution failure whose code was notinvalid_grantwas converted into a storage failure. So a connection with a broken credential never got alast_healthrow, and every probe failed the request.revalidateQuerysentifStaleMsonly for healthy verdicts, so the server-side freshness gate was bypassed for exactly the connections that were failing. The client's once-per-mount guard is per mount, not per connection, so every surface rendering a broken connection sent its own refresh grant to an upstream that was already refusing.In production a handful of broken connections produced hundreds of server errors on the health endpoint plus unbounded refresh traffic to third-party token endpoints.
Fix
Both health paths — credential-only and probing — now fold a credential-resolution failure through the single
healthFromCredentialResolutionFailuremapping and persist the resultingexpired/degradedverdict; the divergenthealthFromCredentialResolutionErroris deleted. The failure channel stays reserved for genuine storage faults, so an infra blip still fails the request rather than persisting as a verdict. The hook always sendsifStaleMs: the long window for a healthy verdict, a short 30s window for anything else.Testing
Two e2e scenarios in
e2e/scenarios/connection-health-verdict.test.ts(run on the cloud target), one per half of the symptom. Both complete a real authorization-code flow against a test authorization server that refuses every refresh, with a health check declared on the integration so the connection takes the probing path.API scenario — the server half:
degradedverdict carrying the AS's reason, not a failed request;invalid_grant) readsexpiredand persists too.Browser scenario — the client half, driven only by navigation between the two surfaces a user actually visits (integration page, integrations list, back again). After the first surface's probe is the baseline, the authorization server's ledger shows no further refresh grant, while the app is still asking — and every automatic health request carries an
ifStaleMswindow.Red against pre-fix product code: the API scenario's health request fails with an InternalError; reverting only the hook leaves the browser scenario failing on the ledger (extra refresh grants) and on the missing
ifStaleMs.One unit test remains,
packages/react/src/lib/use-connection-health.test.ts, pinning the exact window widths per verdict status — cheaper than a browser run per status. The server-side unit tests that duplicated the scenario were removed.Out of scope, deliberately: refresh classification itself, the persistent refresh cool-off, and vault write policy.