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
|
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
|
# WordPress CLI Remote Execution
|
||||||
@@ -17,6 +17,141 @@ Key points:
|
|||||||
depend on this.
|
depend on this.
|
||||||
- Always pass `--path=` explicitly rather than relying on `cd` alone
|
- Always pass `--path=` explicitly rather than relying on `cd` alone
|
||||||
propagating through every nested layer correctly.
|
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
|
## 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
|
needed" reliably produces a cascade of "undefined function/class" errors
|
||||||
that have to be debugged one require statement at a time — reading the
|
that have to be debugged one require statement at a time — reading the
|
||||||
real bootstrap sequence once up front is faster than that cascade.
|
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