Skip to content

Fix plugin reinstall failure after deletion in Sandbox runtime - #4044

Merged
wojtekn merged 7 commits into
trunkfrom
ai/agent/stu-1931-1782979528
Jul 10, 2026
Merged

Fix plugin reinstall failure after deletion in Sandbox runtime#4044
wojtekn merged 7 commits into
trunkfrom
ai/agent/stu-1931-1782979528

Conversation

@agent-sandbox-automattic

Copy link
Copy Markdown
Contributor

Related issues

Closes https://linear.app/a8c/issue/STU-1931

How AI was used in this PR

AI was used to investigate the root cause, design the fix, and write the code.

Proposed Changes

Fixes a bug where manually reinstalling a plugin (via zip upload) after deleting it through the WordPress Plugins admin screen would fail with:

The destination directory already exists and could not be removed.

The workaround was to restart the Studio site before reinstalling.

Root cause: In the Sandbox (PHP WASM) runtime, PHP runs as a persistent process whose internal stat/realpath caches persist across HTTP requests (unlike native PHP, where each request forks a fresh PHP process). After deleting a plugin in one request, PHP's cache can retain a stale "directory exists" result for the plugin path. On the next request (reinstalling the same plugin), WordPress calls is_dir() on the destination — the stale cache says it exists — then tries to delete it and gets an ENOENT-equivalent failure from the VFS layer, producing the error.

Fix: A new mu-plugin (0-clear-stat-cache-before-upgrade.php) hooks into WordPress's upgrader_pre_install filter and calls clearstatcache(true). This flushes both PHP's stat cache and its realpath cache immediately before any upgrade or install operation, ensuring is_dir() gets a fresh result from the real filesystem.

This is a no-op for the native PHP runtime (each request already starts with a clean process and empty caches).

Testing Instructions

  • Create a Studio site using the Sandbox PHP runtime
  • Navigate to wp-admin → Plugins → Add New Plugin → Upload Plugin, and install any plugin via zip (or install from the DotOrg directory)
  • Delete the plugin via the Plugins list
  • Upload/install the same plugin again
  • Before fix: the second install fails with "The destination directory already exists and could not be removed"
  • After fix: the second install succeeds

Pre-merge Checklist

  • Tested the fix against the reproduction steps described in STU-1931 (Sandbox runtime only; native PHP unaffected)
  • No UI/CSS changes — no visual review required
  • CI checks pass

Linear: STU-1931

Co-authored-by: wojtekn wojtek.naruniec@automattic.com

This PR was auto-generated by Agent Sandbox. Please review carefully before merging.

matticbot and others added 5 commits July 2, 2026 08:21
In the Sandbox (PHP WASM) runtime, PHP runs as a persistent process whose
internal stat/realpath caches persist across HTTP requests. After a plugin is
deleted via wp-admin, WordPress calls rmdir() which clears the stat-cache for
that exact path. However on the next request (reinstalling the same plugin) the
path may resolve differently (e.g. with/without trailing slash, via realpath)
and the stale cache entry causes is_dir() to return true for a directory that
no longer exists. WordPress then tries to delete it before installing, gets an
ENOENT-equivalent failure from the VFS, and returns the error "The destination
directory already exists and could not be removed."

Add a mu-plugin that hooks into upgrader_pre_install and calls
clearstatcache(true) to flush both the stat cache and the realpath cache before
any upgrade/install operation. This ensures WordPress gets an accurate
filesystem view and can proceed with the installation.

Co-authored-by: wojtekn <wojtek.naruniec@automattic.com>
@wojtekn wojtekn self-assigned this Jul 8, 2026
@wojtekn
wojtekn marked this pull request as ready for review July 8, 2026 10:20
@wpmobilebot

wpmobilebot commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 270b16e vs trunk

app-size

Metric trunk 270b16e Diff Change
App Size (Mac) 1419.29 MB 1412.19 MB 7.10 MB 🟢 -0.5%

site-editor

Metric trunk 270b16e Diff Change
load 1025 ms 1065 ms +40 ms ⚪ 0.0%

site-startup

Metric trunk 270b16e Diff Change
siteCreation 6526 ms 6484 ms 42 ms ⚪ 0.0%
siteStartup 2385 ms 2399 ms +14 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@wojtekn
wojtekn merged commit 9da2e4d into trunk Jul 10, 2026
11 checks passed
@wojtekn
wojtekn deleted the ai/agent/stu-1931-1782979528 branch July 10, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants