The social sharing entry points conflate two different questions — "does this site support publicize?" (capability) and "can the app reach the v2 Publicize API for this site right now?" (reachability) — and wpAssertionFailure when they disagree. They disagree whenever a publicize-capable blog's WP.com token is unusable, which is a normal runtime state, not a programmer error.
Introduced by #25747: on trunk today socialSharingV2 is debug-only, so production still falls through to the legacy SharingViewController. Once #25747 ships, the legacy fallback is gone and the else branch asserts.
Summary
blog.supports(.publicize) validates capability only. Its auth floor is supportsRestAPI (= account != nil); it does not check that the account's authToken is present/usable.
ManageConnectionsHostingController.make(for:) (→ JetpackSocialFactory.serviceConfiguration(for:)) validates reachability: it additionally requires dotComID > 0 and a non-empty authToken.
- Two entry points treat
make(for:) == nil as an assertion + bail, so a capable-but-unreachable blog gets a debug crash / release no-op where the Sharing screen should be.
Root cause
supports(.publicize) and make(for:) answer different questions and diverge on exactly the broken-auth state the code comment claims to guard:
Blog+Features.swift — supportsPublicize → supportsRestAPI (account != nil) + isPublishingPostsAllowed() + (isHostedAtWPcom and not publicize_permanently_disabled). No token check.
WPAccount.handleInvalidToken(...) nulls authToken but keeps the WPAccount, so supports(.publicize) stays true.
JetpackSocialFactory.serviceConfiguration(for:) requires dotComID > 0 and !authToken.isEmpty → returns nil → make(for:) returns nil.
Affected entry points
- ❌
BlogDetailsViewController+Swift.swift — showSharing(from:): else { return wpAssertionFailure("social connections service unavailable") }
- ❌
SiteStatsInsightsTableViewController.swift — growAudienceEnablePostSharingButtonTapped(): same make(for:) else { wpAssertionFailure(...) }
- ✅
CustomPostSettingsViewModel.resolveSocialConnectionsService(...) already does it correctly — it checks blog.supports(.publicize) and JetpackSocialFactory.shared.connectionsService(for:) != nil, and returns nil gracefully (the section just doesn't render). This is the model the other two should follow.
Repro
- Sign in to a WP.com-hosted blog and confirm Sharing / the Stats grow-audience "Enable post sharing" nudge is available.
- Put the account into the invalid-token state (server-side token revocation / password change, or drive
WPAccount.handleInvalidToken directly — authToken becomes nil, the WPAccount remains).
- Tap My Site → Sharing (or the Stats "Enable post sharing" nudge).
Expected: the sharing/connections screen opens, or the user is routed to re-auth.
Actual: debug build asserts (wpAssertionFailure); release build presents nothing.
Proposed fix (separate PR)
Split the two checks, per @crazytonyli's / review suggestion:
- Capability gates visibility of the entry point → keep
blog.supports(.publicize).
- Reachability is validated at open time by building the service —
ManageConnectionsHostingController.make(for:) / JetpackSocialFactory.shared.connectionsService(for:). Treat nil as an expected outcome, not an assertion. Don't re-derive dotComID/authToken at call sites — the factory is the single source of truth (the two asserting sites drifting from it is what produced this bug).
- On
nil, degrade instead of asserting:
- auth problem → route to re-auth.
blog.isAccessibleThroughWPCom (= account?.wordPressComRestApi != nil) is the existing helper; touching wordPressComRestApi on an empty token already posts .wpAccountRequiresShowingSigninForWPComFixingAuthToken.
- structurally not v2-serviceable → fall back to
SharingButtonsViewController or hide the affordance.
- Consider consolidating the three entry points behind one presenter so the "service unavailable" policy (and the duplicated
"social connections service unavailable" string) lives in one place next to make(for:).
Token validity is ultimately a runtime answer — the client can only check a token is present synchronously; server-side validity is only known once a call returns 200 vs 401 (already handled by wordPressComRestApi.setInvalidTokenHandler). So the model is three tiers — capable → service constructible → first loadConnections() doesn't 401 — degrading at each, asserting at none.
Test plan
Related (out of scope for this issue)
Same PR (#25747) also drops the grow-audience Publicize nudge's completion path — the deleted SharingViewControllerDelegate.didChangePublicizeServices() was the only caller of markCurrentNudgeAsCompleted() for that flow, so the nudge no longer auto-dismisses after connecting and the .statsPublicizeNudgeCompleted Tracks event is now never fired. Worth a separate follow-up (re-add a completion signal, or mark complete on present).
The social sharing entry points conflate two different questions — "does this site support publicize?" (capability) and "can the app reach the v2 Publicize API for this site right now?" (reachability) — and
wpAssertionFailurewhen they disagree. They disagree whenever a publicize-capable blog's WP.com token is unusable, which is a normal runtime state, not a programmer error.Introduced by #25747: on
trunktodaysocialSharingV2is debug-only, so production still falls through to the legacySharingViewController. Once #25747 ships, the legacy fallback is gone and theelsebranch asserts.Summary
blog.supports(.publicize)validates capability only. Its auth floor issupportsRestAPI(=account != nil); it does not check that the account'sauthTokenis present/usable.ManageConnectionsHostingController.make(for:)(→JetpackSocialFactory.serviceConfiguration(for:)) validates reachability: it additionally requiresdotComID > 0and a non-emptyauthToken.make(for:) == nilas an assertion + bail, so a capable-but-unreachable blog gets a debug crash / release no-op where the Sharing screen should be.Root cause
supports(.publicize)andmake(for:)answer different questions and diverge on exactly the broken-auth state the code comment claims to guard:Blog+Features.swift—supportsPublicize→supportsRestAPI(account != nil) +isPublishingPostsAllowed()+ (isHostedAtWPcomand notpublicize_permanently_disabled). No token check.WPAccount.handleInvalidToken(...)nullsauthTokenbut keeps theWPAccount, sosupports(.publicize)staystrue.JetpackSocialFactory.serviceConfiguration(for:)requiresdotComID > 0and!authToken.isEmpty→ returnsnil→make(for:)returnsnil.Affected entry points
BlogDetailsViewController+Swift.swift—showSharing(from:):else { return wpAssertionFailure("social connections service unavailable") }SiteStatsInsightsTableViewController.swift—growAudienceEnablePostSharingButtonTapped(): samemake(for:) else { wpAssertionFailure(...) }CustomPostSettingsViewModel.resolveSocialConnectionsService(...)already does it correctly — it checksblog.supports(.publicize)andJetpackSocialFactory.shared.connectionsService(for:) != nil, and returnsnilgracefully (the section just doesn't render). This is the model the other two should follow.Repro
WPAccount.handleInvalidTokendirectly —authTokenbecomesnil, theWPAccountremains).Expected: the sharing/connections screen opens, or the user is routed to re-auth.
Actual: debug build asserts (
wpAssertionFailure); release build presents nothing.Proposed fix (separate PR)
Split the two checks, per @crazytonyli's / review suggestion:
blog.supports(.publicize).ManageConnectionsHostingController.make(for:)/JetpackSocialFactory.shared.connectionsService(for:). Treatnilas an expected outcome, not an assertion. Don't re-derivedotComID/authTokenat call sites — the factory is the single source of truth (the two asserting sites drifting from it is what produced this bug).nil, degrade instead of asserting:blog.isAccessibleThroughWPCom(=account?.wordPressComRestApi != nil) is the existing helper; touchingwordPressComRestApion an empty token already posts.wpAccountRequiresShowingSigninForWPComFixingAuthToken.SharingButtonsViewControlleror hide the affordance."social connections service unavailable"string) lives in one place next tomake(for:).Token validity is ultimately a runtime answer — the client can only check a token is present synchronously; server-side validity is only known once a call returns 200 vs 401 (already handled by
wordPressComRestApi.setInvalidTokenHandler). So the model is three tiers — capable → service constructible → firstloadConnections()doesn't 401 — degrading at each, asserting at none.Test plan
!blog.supports(.publicize)blog → still getsSharingButtonsViewController.wpAssertionFailureon any reachable auth state.Related (out of scope for this issue)
Same PR (#25747) also drops the grow-audience Publicize nudge's completion path — the deleted
SharingViewControllerDelegate.didChangePublicizeServices()was the only caller ofmarkCurrentNudgeAsCompleted()for that flow, so the nudge no longer auto-dismisses after connecting and the.statsPublicizeNudgeCompletedTracks event is now never fired. Worth a separate follow-up (re-add a completion signal, or mark complete on present).