Merge multisite/blast-radius safety practices into wordpress-cli-remote-execution
Both the skills.sh and autoskills.sh scans (dispatched this session, see
granja/_temp/codex-logs/{skills-sh-scan,autoskills-sh-scan}.md) independently
flagged wordpress/agent-skills' official wp-wpcli-and-ops module as high-value
source material for this skill: safe search-replace, db export/import,
multisite targeting, and deterministic environment inspection before any
destructive command.
Merged in (adapted to this fleet's bastille cmd + su -m www invocation model,
not the upstream's local/SSH-direct WP-CLI assumption):
- pre-write environment/blast-radius inspection sequence (plain wp-cli calls
through the existing invocation pattern, not the upstream's Node.js
wpcli_inspect.mjs script)
- multisite targeting checklist (--url requirement, site list iteration)
- safe search-replace/domain-migration workflow (backup, dry-run, flush)
- db export/import, plugin/theme, and cron/cache-flush guardrails
Ties the upstream --allow-root warning to this fleet's own documented
wp-content ownership-drift incident (docs/server-granja.md) and cites the
real jail counts/CVE rollout from docs/server-granja.md, server-staging.md,
and server-gringo.md for grounding. Provenance noted in the file with source
URLs. Deliberately left out generic plugin-development/performance/PHPStan
material from the same upstream repo — out of scope for this remote-execution
skill.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: wordpress-cli-remote-execution
|
||||
description: Use when running wp-cli commands against a WordPress site living inside a remote jail/container (via ssh + a jail-exec wrapper). Covers the output-prefix stripping gotcha and the correct user/path invocation pattern.
|
||||
description: Use when running wp-cli commands against a WordPress site living inside a remote jail/container (via ssh + a jail-exec wrapper). Covers the output-prefix stripping gotcha, the correct user/path invocation pattern, pre-write environment/blast-radius inspection, multisite targeting, and safe search-replace/db-export-import/cache-flush workflows.
|
||||
---
|
||||
|
||||
# WordPress CLI Remote Execution
|
||||
@@ -17,6 +17,141 @@ Key points:
|
||||
depend on this.
|
||||
- Always pass `--path=` explicitly rather than relying on `cd` alone
|
||||
propagating through every nested layer correctly.
|
||||
- This is not a theoretical concern on this fleet: `docs/server-granja.md`
|
||||
documents a real incident where `wp-content` (the `upgrade` dir,
|
||||
`languages/`, and several plugin dirs) drifted to `root:www` ownership
|
||||
from `wp` commands run as root via `bastille cmd ... wp ... --allow-root`
|
||||
instead of switching to `www` first, which then broke every
|
||||
admin-initiated core/plugin update until a `chown -R www:www` fixed it.
|
||||
Prefer the `su -m www` pattern above over `--allow-root`-as-root for
|
||||
exactly this reason.
|
||||
|
||||
## Guardrails: confirm environment and blast radius before any write
|
||||
|
||||
Before running anything that writes (plugin/theme changes, `search-replace`,
|
||||
`db import`/`db reset`, bulk deletes, cron triggers, cache/rewrite flushes
|
||||
on a busy site), confirm you're pointed at the right target. This fleet
|
||||
runs 13+ real WordPress jails on granja alone (`docs/server-granja.md`),
|
||||
plus more on staging and gringo (`docs/server-staging.md`,
|
||||
`docs/server-gringo.md`) — mixing up a jail name or `--path` when several
|
||||
`ssh`/`bastille` panes are open is the easiest way to write to the wrong
|
||||
site. Don't assume production is safe to write to just because a command
|
||||
"should" be harmless.
|
||||
|
||||
Run this sequence (through the invocation pattern above, one `wp` call at
|
||||
a time so each exit code/output is unambiguous) before the real operation:
|
||||
|
||||
```bash
|
||||
# 1. Confirm wp-cli actually resolves at this exact path/jail
|
||||
ssh user@host "bastille cmd <jail-name> sh -c 'su -m www -c \"wp --path=/path/to/site core is-installed\"'"
|
||||
|
||||
# 2. Confirm which site this really is — read it back, don't assume from the jail name
|
||||
ssh user@host "bastille cmd <jail-name> sh -c 'su -m www -c \"wp --path=/path/to/site option get siteurl\"'"
|
||||
|
||||
# 3. Confirm core version, for context
|
||||
ssh user@host "bastille cmd <jail-name> sh -c 'su -m www -c \"wp --path=/path/to/site core version\"'"
|
||||
|
||||
# 4. Confirm single-site vs multisite BEFORE choosing --url targeting (see below)
|
||||
ssh user@host "bastille cmd <jail-name> sh -c 'su -m www -c \"wp --path=/path/to/site core is-installed --network\"'"
|
||||
```
|
||||
|
||||
Step 4's exit code is the signal: `0` means the install is network-activated
|
||||
(multisite); nonzero means an ordinary single site. Don't skip this check
|
||||
and assume — this fleet's WordPress jails haven't all been surveyed for
|
||||
multisite, and treating a multisite install as single-site (or vice versa)
|
||||
is exactly the kind of mistake that silently affects the wrong site.
|
||||
|
||||
## Multisite targeting checklist
|
||||
|
||||
If step 4 above confirms multisite (or you're unsure and haven't checked):
|
||||
|
||||
- Every per-site command needs an explicit `--url=<site-url>` — an omitted
|
||||
`--url` on a multisite install can operate on the network's primary site
|
||||
instead of the one you actually meant, with no error to warn you.
|
||||
- `wp --path=/path/to/site site list` to see every site in the network
|
||||
before touching any of them.
|
||||
- `wp --path=/path/to/site option get siteurl --url=<site-url>` to confirm
|
||||
you're targeting the intended site within the network.
|
||||
- For a change that's meant to apply network-wide, prefer scripting it as
|
||||
list-then-iterate (`site list` → loop → run the safe per-site command)
|
||||
over trusting a single `--network`-flagged command to do the right thing
|
||||
for every site's data — a loop you can log and interrupt is easier to
|
||||
recover from than one opaque network-wide mutation.
|
||||
|
||||
The jail audits in `docs/server-granja.md`, `docs/server-staging.md`, and
|
||||
`docs/server-gringo.md` map each jail to a single domain via
|
||||
`wp option get siteurl` — but that check confirms the site's URL, not
|
||||
whether the install itself is network-activated. None of those audits ran
|
||||
`core is-installed --network`, so multisite status per jail is genuinely
|
||||
unverified, not "probably fine because it's one domain per jail." Run
|
||||
step 4 above rather than assuming either way for a jail you haven't
|
||||
personally checked.
|
||||
|
||||
## Safe `wp search-replace` / domain migration workflow
|
||||
|
||||
Follow this sequence for any URL/domain change, protocol switch
|
||||
(`http://` → `https://`), or path migration — don't run `search-replace`
|
||||
directly against production without it:
|
||||
|
||||
1. **Backup first:** `wp --path=/path/to/site db export` (through the
|
||||
invocation pattern above) before touching anything. Confirm the backup
|
||||
file actually landed and is non-empty — `wordpress-plugin-staging-verification`
|
||||
documents a cosmetic `proc_open`/`posix_spawn` error some `wp-cli` write
|
||||
commands print in this sandbox even when the underlying operation
|
||||
actually succeeded (and, less often, the reverse) — don't trust the
|
||||
command's own success/failure text alone for something this important;
|
||||
check the file.
|
||||
2. **Dry run:** `wp search-replace 'OLD' 'NEW' --dry-run --all-tables-with-prefix`
|
||||
and read the reported row count before doing anything else.
|
||||
3. **Real replace:** the same command minus `--dry-run`. Add
|
||||
`--skip-columns=...` for any known binary/blob columns, and `--precise`
|
||||
if the dry run surfaced questionable matches.
|
||||
4. **Flush after:** `wp cache flush` then `wp rewrite flush` — a stale
|
||||
object cache or stale rewrite rules are the most common "why does the
|
||||
old URL still show up" symptom after a migration that actually worked
|
||||
fine at the DB level.
|
||||
|
||||
On multisite, decide up front whether the replace is scoped to one site
|
||||
(`--url=...`) or the whole network (iterate `wp site list`, per the
|
||||
checklist above) — the same "which site did this actually touch" risk
|
||||
applies here as everywhere else in this skill.
|
||||
|
||||
## DB export/import guardrails
|
||||
|
||||
- `wp db export` before any of the high-risk operations below — cheap
|
||||
insurance, always take it.
|
||||
- `wp db import` **overwrites the target database wholesale.** Re-confirm
|
||||
the jail/site one more time immediately before running it (repeat step 2
|
||||
of the guardrails sequence above) — this is the single easiest way to
|
||||
destroy the wrong site's data when several `ssh`/`bastille` sessions are
|
||||
open at once across this fleet's jails.
|
||||
- Treat these as requiring explicit confirmation before running, same as
|
||||
bulk deletes (`wp post delete --force --all`, `wp user delete --reassign`)
|
||||
and mass plugin/theme updates on a live production jail.
|
||||
|
||||
## Plugin/theme operations guardrails
|
||||
|
||||
- `wp plugin list` / `wp theme list` first, to see current state before
|
||||
changing it.
|
||||
- Avoid `wp plugin update --all` / `wp theme update --all` against a
|
||||
production jail without a deliberate maintenance window — this fleet's
|
||||
own 2026-08-12 fleet-wide core-security rollout (CVE-2026-65640,
|
||||
`docs/server-granja.md`) was done as a coordinated pass across all 13
|
||||
jails, not a casual one-off `update --all`.
|
||||
- On multisite, plugin activation can be per-site or network-activated —
|
||||
confirm which one is intended (see the multisite checklist above) before
|
||||
running `wp plugin activate`.
|
||||
|
||||
## Cron and cache/rewrite flush guardrails
|
||||
|
||||
- `wp cron event list` to see what's actually scheduled before running
|
||||
anything.
|
||||
- `wp cron event run <hook>` to run one specific event for debugging,
|
||||
rather than triggering every scheduled event at once.
|
||||
- `wp cache flush` / `wp rewrite flush` are generally low-risk but can
|
||||
cause a brief load spike on a high-traffic content site — worth
|
||||
confirming intent rather than flushing reflexively as a first debugging
|
||||
step.
|
||||
|
||||
## GOTCHA: jail-exec wrappers prefix their own output
|
||||
|
||||
@@ -67,3 +202,22 @@ your standalone script. Skipping steps because "this part probably isn't
|
||||
needed" reliably produces a cascade of "undefined function/class" errors
|
||||
that have to be debugged one require statement at a time — reading the
|
||||
real bootstrap sequence once up front is faster than that cascade.
|
||||
|
||||
## Provenance
|
||||
|
||||
The guardrails/blast-radius sequencing, multisite targeting checklist,
|
||||
and safe search-replace/db-export-import/cache-flush workflow above were
|
||||
adapted from WordPress's own official `wp-wpcli-and-ops` skill
|
||||
(https://github.com/WordPress/agent-skills/tree/trunk/skills/wp-wpcli-and-ops,
|
||||
also listed at https://skills.sh/wordpress/agent-skills/wp-wpcli-and-ops),
|
||||
reworked for this fleet's `bastille cmd` + `su -m www` invocation model
|
||||
rather than the upstream skill's assumption of local or SSH-direct WP-CLI
|
||||
access. Its bundled `wpcli_inspect.mjs` Node.js inspector script was not
|
||||
copied — this fleet doesn't run wp-cli operations through Node tooling —
|
||||
the same inspection sequence is reproduced above as plain `wp` calls
|
||||
through the existing invocation pattern instead. Notably, the upstream
|
||||
skill's own debugging notes independently warn against `--allow-root`
|
||||
"unless you understand the environment and have no alternative," which
|
||||
lines up with the real `wp-content` ownership-drift incident cited in the
|
||||
Invocation pattern section above — two independent sources converging on
|
||||
the same conclusion for this fleet.
|
||||
|
||||
Reference in New Issue
Block a user