Wpcomsh recovery-mode sync: include per-extension error info#48440
Conversation
Follow-up to #48213. The state snapshot now also carries an extracted view of the live *_paused_extensions option, so wpcom-side consumers (Calypso) can surface what fataled instead of just that something fataled. Each record carries kind/slug/version + errno/message/file/line plus the transportable signature token from #48369, so a fatal seen via the recovery email and via the wpcomsh fatal-error screen can be joined on the same opaque token. file is reduced to its basename so server paths don't leak. Reading from the live option on every snapshot (instead of stashing errors in our own option, or threading them through one capture path) means every POST — email / session-start / session-end — emits a complete state, and session-end naturally shows errors=[] without any explicit clear step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! Wpcomsh plugin:
If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack. |
Code Coverage SummaryCoverage changed in 1 file.
Coverage check overridden by
Coverage tests to be added later
|
The new recovery_session_errors field is an array of records, so the existing array<string,int> phpdoc no longer fits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The flat fields (kind/slug/version/errno/message/file/line) cover the Calypso display use case. The signature was for cross-surface analytics joining (recovery email vs. fatal-error screen logstash), which has no consumer yet. We can re-add when one materializes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
So the fatal-request POST already carries the error info, instead of waiting for the admin to click the recovery email link. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| } | ||
| $normalized = wp_normalize_path( $file ); | ||
|
|
||
| if ( defined( 'WP_PLUGIN_DIR' ) ) { |
There was a problem hiding this comment.
It would be better to follow the implementation from core's get_extension_for_error to capture exactly what is stored in *_paused_extensions
if ( defined( 'WP_PLUGIN_DIR' ) ) {
$plugin_dir = wp_normalize_path( WP_PLUGIN_DIR ) . '/';
if ( str_starts_with( $normalized, $plugin_dir ) ) {
$rel = substr( $normalized, strlen( $plugin_dir ) );
$parts = explode( '/', $rel );
return array( 'plugin', $parts[0] );
}
}
There was a problem hiding this comment.
Good catch — switched to core's pattern ($parts[0] under WP_PLUGIN_DIR) so the slug we capture pre-session matches the key WP itself stores in *_paused_extensions once a session opens. Dropped the get_plugins() walk since we no longer need the main-file path here. Pushed in bacb8612be.
Use the first path segment under WP_PLUGIN_DIR as the plugin slug — the same value WP_Recovery_Mode::get_extension_for_error() produces and the key WP itself uses inside *_paused_extensions. Previously we returned the main-file path (e.g. akismet/akismet.php), which would not match the slug stored once a session is created for the same fatal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tic#48440) * wpcomsh recovery-mode sync: include per-extension error info Follow-up to Automattic#48213. The state snapshot now also carries an extracted view of the live *_paused_extensions option, so wpcom-side consumers (Calypso) can surface what fataled instead of just that something fataled. Each record carries kind/slug/version + errno/message/file/line plus the transportable signature token from Automattic#48369, so a fatal seen via the recovery email and via the wpcomsh fatal-error screen can be joined on the same opaque token. file is reduced to its basename so server paths don't leak. Reading from the live option on every snapshot (instead of stashing errors in our own option, or threading them through one capture path) means every POST — email / session-start / session-end — emits a complete state, and session-end naturally shows errors=[] without any explicit clear step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Phan: widen \$payload type to array<string,mixed> The new recovery_session_errors field is an array of records, so the existing array<string,int> phpdoc no longer fits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop signature from recovery-mode-sync error records The flat fields (kind/slug/version/errno/message/file/line) cover the Calypso display use case. The signature was for cross-surface analytics joining (recovery email vs. fatal-error screen logstash), which has no consumer yet. We can re-add when one materializes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop signature mention from changelog entry Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Capture error_get_last() at email-send time So the fatal-request POST already carries the error info, instead of waiting for the admin to click the recovery email link. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Recovery sync: match core's slug shape in resolve_extension_for_file Use the first path segment under WP_PLUGIN_DIR as the plugin slug — the same value WP_Recovery_Mode::get_extension_for_error() produces and the key WP itself uses inside *_paused_extensions. Previously we returned the main-file path (e.g. akismet/akismet.php), which would not match the slug stored once a session is created for the same fatal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: dognose24 <6869813+dognose24@users.noreply.github.com>
Proposed changes
Follow-up to #48213. The recovery-mode state snapshot now also carries a normalized list of per-extension errors so wpcom-side consumers (Calypso) can surface what fataled instead of just that something fataled.
Each record carries:
kind—pluginorthemeslug— extension slug as recorded by WP recovery modeversion— resolved viaget_plugins()/wp_get_theme()errno,message,line— straight from WP's captured error arrayfile— reduced to the basename so server paths don't leakWhere the errors come from
Two sources, picked in order:
*_paused_extensionsoption — once the admin clicks the recovery email link, WP creates a session and writes paused extensions there. We read it live on every snapshot viawp_recovery_mode()->get_session_id().error_get_last()fallback — on the fatal request itself, before the admin has clicked the link, no paused-extensions option exists yet. We're called inside WP's fatal-handler shutdown stack (viaupdate_option_recovery_mode_email_last_sent), soerror_get_last()still holds the original fatal. We resolve the file path back to a plugin/theme slug and emit one record from that, so the very first POST already carries the error info.The fallback only fires when WP actually sends a recovery email (rate-limited to ~once/day, skipped if a session is already active). Subsequent fatals are picked up via the paused-extensions path once the admin enters recovery.
Design choice
Errors live in the snapshot rather than a wpcomsh-side option — symmetric with how entered_at / exited_at are read. No new option, no thread-through arg.
Consequences:
[]— wpcom naturally sees the state cleared.Privacy
messageandlineare admin-visible on WP's recovery email and recovery screen, so the new exposure is the same content already shown to the site admin.fileis reduced to its basename to avoid leaking server-internal paths. No user identifiers, request URIs, stack traces, or PII.Does this pull request change what data or activity we track or use?
Yes. This PR extends the existing
recovery-mode-statusPOST (added in #48213) with a newrecovery_session_errorsarray containing per-extension fatal info:kind(plugin/theme),slug,version,errno,message,file(basename only), andline. The data is sourced from WP's own recovery-mode state (*_paused_extensionsoption, orerror_get_last()during the fatal-handler shutdown) and mirrors what's already shown to the site admin in WP's recovery email and recovery screen. No user identifiers, request URIs, stack traces, or PII are added.Testing instructions
jetpack rsync plugins/wpcomsh <site>).trigger_error( 'boom', E_USER_ERROR );)./sites/<blog_id>/recovery-mode-statusalready includesrecovery_session_errorswith one entry —{kind: \"plugin\", slug: <slug>, version: <ver>, errno: <int>, message: \"boom\", file: \"<basename>.php\", line: <int>}. (This is theerror_get_last()fallback path.)recovery_session_errorsarray, now sourced from*_paused_extensions.recovery_session_errors: [].Related
recovery_session_errorsfield onjetpack_recovery_mode_status; this PR alone won't make it visible in the GET sites response.🤖 Generated with Claude Code