Skip to content

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

@jkmassel

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions