Skip to content

Populate missing wpApiRestUrl during editor preload via WpLoginClient.apiDiscovery #22899

Description

@jkmassel

Summary

Some sites in the local DB have WP_API_REST_URL (the @Column-mapped SiteModel.mWpApiRestUrl) set to NULL despite having valid application-password credentials. GutenbergKitSettingsBuilder.buildPostConfiguration reads this field with a fallback:

val siteApiRoot = if (shouldUseWPComRestApi) {
    WPCOM_API_ROOT
} else {
    site.wpApiRestUrl ?: "${site.url}/wp-json/"
}

For standard hosts (e.g. *.wpcomstaging.com, most self-hosted setups) the fallback happens to construct the same URL discovery would have produced. For non-standard REST roots — plugins that rewrite the REST namespace, wp-json under a subdirectory, sites that report a different apiRootUrl during discovery — the fallback is wrong and the editor will 404 on /wp-block-editor/v1/settings.

Evidence

Observed during on-device testing of #22893. DB query against the dev device:

 386|https://vanilla.wpmt.co                              |u=28|p=60|url=32  ✓
1157|https://jetpack.wpmt.co                              |u=28|p=60|url=32  ✓
1215|https://automatticwidgets.wpcomstaging.com           |u=32|p=60|url=51  ✓
1331|https://jeremyseriousbusinesstesting.wpcomstaging.com|u=32|p=60|url=    ✗ NULL

jeremyseriousbusinesstesting.wpcomstaging.com is an Atomic site with working credentials but a NULL WP_API_REST_URL. The editor still works because the fallback https://jeremyseriousbusinesstesting.wpcomstaging.com/wp-json/ happens to be correct — but this is a coincidence of the host pattern, not by design.

The root cause is upstream: somewhere between initial site fetch and application-password setup, wpApiRestUrl either never gets populated for some sites or gets cleared by an unrelated code path. A controlled experiment (manually populate URL → run wipe+mint → verify) confirmed the field survives cleanly through the validator hardening in #22893. So the clearing is conditional on a live-state condition that wasn't isolated in a single testing session.

Proposed fix

wordpress-rs already exposes WpLoginClient.apiDiscovery(siteUrl): ApiDiscoveryResult (used today by ApplicationPasswordLoginHelper). Use it in GutenbergEditorPreloader.preloadIfNeeded to populate wpApiRestUrl lazily when missing:

if (site.wpApiRestUrl.isNullOrEmpty()) {
    when (val result = wpLoginClient.apiDiscovery(site.url)) {
        is ApiDiscoveryResult.Success -> {
            site.wpApiRestUrl = result.success.apiRootUrl.toString()
            dispatcher.dispatch(SiteActionBuilder.newUpdateSiteAction(site))
        }
        else -> { /* log, fall through to existing fallback */ }
    }
}

preloadIfNeeded runs in scope.launch(bgDispatcher) and is invoked on every buildDashboardOrSiteItems call, so this catches the missing URL before the editor opens. Idempotent, no UI impact.

Also worth considering: populate the URL on the headless-mint success path in ApplicationPasswordViewModelSlice so newly-minted sites get it too. The mint goes through the Jetpack tunnel which doesn't perform discovery — that's how new Atomic mints can end up without a URL in the first place.

Out of scope for this issue

  • Root cause investigation into why specific sites' URLs end up NULL after first setup
  • Whether the EditorHTTPClient concurrent 401 during a validator wipe window is worth fixing — that's credential staleness, not URL.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions