Skip to content

Fix false connectivity banner on private Atomic sites#22926

Open
jkmassel wants to merge 5 commits into
release/26.8from
fix/atomic-private-site-capability-detection
Open

Fix false connectivity banner on private Atomic sites#22926
jkmassel wants to merge 5 commits into
release/26.8from
fix/atomic-private-site-capability-detection

Conversation

@jkmassel
Copy link
Copy Markdown
Contributor

@jkmassel jkmassel commented Jun 1, 2026

Description

Fixes a regression where the My Site "Unable to connect to your site" banner appears for private WP.com Atomic sites even though the site is fully reachable. Introduced by #22883 (present in both trunk and release/26.8).

There's a narrow case where this fix doesn't work:

  • An atomic site
  • That's private
  • That uses a plugin or configuration change to move the REST API root away from /wp-json

This is caused by #22905.

Root cause

The banner is driven by EditorSettingsRepository.fetchEditorCapabilitiesForSite(), which runs two probes and requires both to pass. For Atomic sites the route-support probe was changed in #22883 to run REST API autodiscovery against the site's own host instead of the WP.com proxy:

wpLoginClient.apiDiscovery(site.url)

WpLoginClient is the login-flow client — it's constructed with no credentials (ApplicationModule.provideWpLoginClient), so that discovery call is unauthenticated. A private Atomic site gates anonymous requests to its host, so discovery fails (FailureFetchAndParseApiRoot), the route probe returns false, and the banner renders — even though the proxy-backed theme probe in the same fetch succeeds.

Confirmed on device against a private Atomic site, where a single refresh shows the failure is specifically the unauthenticated probe:

Request Transport Auth Result
App-password validation direct host Basic auth ✅ Success
Theme fetch WP.com proxy bearer token ✅ Success
Capability discovery direct host none FailureFetchAndParseApiRoot

Fix

When direct-host discovery fails and the site has application-password credentials, fall back to an authenticated probe against the same direct host using the app-password (Basic auth) client — the transport the editor actually uses on Atomic. Routes are read from apiRoot().get() and resolved against the direct host, mirroring fetchRouteSupportViaConfiguredClient but bypassing the WP.com proxy. Public Atomic sites are unchanged (discovery still succeeds on the first try); sites without app-password credentials are unchanged (nothing to authenticate with → failure, as before).

Verified on-device: after the fix the banner no longer appears on a private Atomic site — discovery still fails, the authenticated probe succeeds, and capability detection completes.

  • EditorSettingsRepository: fetchRouteSupportViaDirectHostDiscovery falls back to a new fetchRouteSupportViaApplicationPasswordClient when discovery fails and hasApplicationPasswordCredentials() is true.
  • WpApiClientProvider: adds getDirectHostApiUrlResolver(site) — the resolver counterpart to getApplicationPasswordClient, so routes fetched through that client resolve against the host they came from.

What we explored

  1. Authenticated fallback after discovery fails ✅ — chosen. Minimal behaviour change: public Atomic keeps the exact tested path; only the broken private case gains the authenticated retry.
  2. Prefer the app-password probe whenever credentials exist (skip discovery). Saves a round-trip on private sites but changes behaviour for public app-password sites and loses discovery's "don't assume /wp-json" robustness for the common case.
  3. Fall back to the WP.com proxy's route list. Reintroduces exactly the proxy/direct-host divergence Probe direct host for editor capability detection on Atomic sites #22883 fixed (Editor fails to load on Atomic sites when theme-styles capability over-reports availability #22879).

Out of scope

The deeper issue — a user-facing "site unreachable" banner being driven by an editor-capability probe on a different transport — is left as a follow-up. Atomic sites with no app-password credentials whose host blocks anonymous discovery still surface the banner; there's no credential to authenticate the probe with.

Testing instructions

Private Atomic site (the regression):

  1. Sign into a WP.com account with a private Atomic site that has an application password
  2. Open My Site, pull to refresh, let the dashboard settle.
  • Verify the "Unable to connect to your site" banner does not appear.
  1. Create a new post.
  • Verify the editor opens correctly.

Public Atomic site (regression check):

  1. Select a public Atomic site (e.g. automatticwidgets.wpcomstaging.com).
  2. Open My Site, pull to refresh.
  • Verify no banner and capability detection still completes.

Simple site (regression check):

  1. Select a WP.com Simple site, open My Site, pull to refresh.
  • Verify behaviour is unchanged.

Unit tests:

  • ./gradlew :WordPress:testJetpackDebugUnitTest --tests EditorSettingsRepositoryTest — 11 pass (9 existing + 2 new covering the authenticated fallback).

@dangermattic
Copy link
Copy Markdown
Collaborator

dangermattic commented Jun 1, 2026

1 Warning
⚠️ This PR is assigned to the milestone 26.8 ❄️. The due date for this milestone has already passed.
Please assign it to a milestone with a later deadline or check whether the release for this milestone has already been finished.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Jun 1, 2026

App Icon📲 You can test the changes from this Pull Request in Jetpack Android by scanning the QR code below to install the corresponding build.

App NameJetpack Android
Build TypeDebug
Versionpr22926-d27828b
Build Number1493
Application IDcom.jetpack.android.prealpha
Commitd27828b
Installation URL3opjk608l9ug0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Jun 1, 2026

App Icon📲 You can test the changes from this Pull Request in WordPress Android by scanning the QR code below to install the corresponding build.

App NameWordPress Android
Build TypeDebug
Versionpr22926-d27828b
Build Number1493
Application IDorg.wordpress.android.prealpha
Commitd27828b
Installation URL6pqcgenmqmfi8
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Copy Markdown
Contributor

wpmobilebot commented Jun 1, 2026

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

@jkmassel jkmassel force-pushed the fix/atomic-private-site-capability-detection branch 2 times, most recently from 85cf367 to b964f35 Compare June 1, 2026 21:16
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

❌ Patch coverage is 77.08333% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.34%. Comparing base (8c993a6) to head (d27828b).

Files with missing lines Patch % Lines
...id/ui/accounts/login/CredentialsChangedNotifier.kt 0.00% 7 Missing ⚠️
...s/android/repositories/EditorSettingsRepository.kt 92.00% 1 Missing and 1 partial ⚠️
...nnectivity/SiteConnectivityBannerViewModelSlice.kt 90.90% 0 Missing and 1 partial ⚠️
...fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff                @@
##           release/26.8   #22926      +/-   ##
================================================
+ Coverage         37.33%   37.34%   +0.01%     
================================================
  Files              2319     2320       +1     
  Lines            124674   124714      +40     
  Branches          16951    16959       +8     
================================================
+ Hits              46551    46580      +29     
- Misses            74361    74370       +9     
- Partials           3762     3764       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Editor capability detection on Atomic sites probes the direct host via
unauthenticated REST API discovery (added in #22883). Private Atomic
hosts gate anonymous requests, so discovery fails and the My Site
"Unable to connect to your site" banner appears even though the site is
reachable. Fall back to an authenticated app-password probe against the
same host when discovery fails and credentials exist.
@jkmassel jkmassel force-pushed the fix/atomic-private-site-capability-detection branch from b964f35 to fd57211 Compare June 1, 2026 22:58
@jkmassel jkmassel requested a review from nbradbury June 2, 2026 01:36
@jkmassel jkmassel added this to the 26.8 ❄️ milestone Jun 2, 2026
@jkmassel jkmassel self-assigned this Jun 2, 2026
@jkmassel jkmassel marked this pull request as ready for review June 2, 2026 01:47
@nbradbury
Copy link
Copy Markdown
Contributor

@jkmassel I still see this banner immediately after logging in.

banner

However, the banner disappears if I pull-to-refresh.

@nbradbury
Copy link
Copy Markdown
Contributor

@jkmassel Claude identified this as a minor issue worth resolving before merging:

silent failure of the authenticated fallback (EditorSettingsRepository.kt:186-191) The non-success branch in fetchRouteSupportViaApplicationPasswordClient returns false with no log. If the banner reappears in the wild, there will be nothing in AppLog distinguishing "discovery failed, no creds" from "discovery failed AND authenticated probe failed." A one-line AppLog.w mirroring the discovery-failure log would make this diagnosable.

The authenticated app-password fallback previously returned a silent
`false` on a non-success response, and the no-credentials path was also
silent. Log both (mirroring the discovery-failure log) so a recurrence
of the connectivity banner can be diagnosed from AppLog rather than
being indistinguishable from "discovery failed, no creds".
@jkmassel
Copy link
Copy Markdown
Contributor Author

jkmassel commented Jun 2, 2026

@jkmassel Claude identified this as a minor issue worth resolving before merging:

silent failure of the authenticated fallback (EditorSettingsRepository.kt:186-191) The non-success branch in fetchRouteSupportViaApplicationPasswordClient returns false with no log. If the banner reappears in the wild, there will be nothing in AppLog distinguishing "discovery failed, no creds" from "discovery failed AND authenticated probe failed." A one-line AppLog.w mirroring the discovery-failure log would make this diagnosable.

Addressed by bf2142e

jkmassel added 3 commits June 2, 2026 14:29
On first login an Atomic site's application password is minted
asynchronously on the My Site screen, so the connectivity capability
fetch can race ahead of it and fail purely for lack of credentials —
flashing a false "unable to connect" banner that clears on the next
refresh. Treat a credential-less fetch as pending rather than a
connection failure, mirroring the existing offline suppression; the
application-password card already owns the no-credentials state.
The connectivity banner's capability fetch and the application-password
mint both run on the My Site screen with no ordering, so on first login
the fetch loses the race and capability flags aren't detected until the
next resume/refresh. Add an app-scoped CredentialsChangedNotifier that
both credential-establishment paths (headless mint and interactive
login) emit to, and have the connectivity slice re-run detection on it —
re-reading a fresh site so it observes the just-persisted credentials.
This also closes the asymmetry where only the interactive path announced
credential changes globally.
The four-way suppression condition tripped detekt's ComplexCondition
threshold once the pending-credentials term was added. Lift it into a
named `suppressBanner` flag — clearer intent, and out of the
if-condition detekt inspects. No behavior change.
@jkmassel
Copy link
Copy Markdown
Contributor Author

jkmassel commented Jun 2, 2026

@jkmassel I still see this banner immediately after logging in.

banner However, the banner disappears if I pull-to-refresh.

This was a race between the application password being created and the checks happening.

It's fixed by 3a10158 and ce19bee.

You can test this by logging out of the app entirely, then logging back in and choosing that site - you shouldn't see the banner.

@nbradbury
Copy link
Copy Markdown
Contributor

@jkmassel This is working for me and I'll approve it, but you may want to address this comment from Claude:

PR description contradicts the implementation. The "Out of scope" section says:

▎ Atomic sites with no app-password credentials whose host blocks anonymous discovery still surface the banner

But isAwaitingApplicationPassword(site) = site.isWPComAtomic && !site.hasApplicationPasswordCredentials() (EditorSettingsRepository.kt:45-46) suppresses the banner in exactly that case. Either update the description, or — more importantly — confirm the intended behavior:

  • If the headless mint (app password creation) permanently fails (not just "in progress"), the user is left with a private Atomic site, no creds, and no banner ever shown. That seems like a regression of its own. Today there's no signal distinguishing "mint in progress" from "mint failed terminally" in isAwaitingApplicationPassword. Worth confirming this is acceptable, or gating on a more precise signal from ApplicationPasswordViewModelSlice.

Copy link
Copy Markdown
Contributor

@nbradbury nbradbury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants