Skip to content

feat(cli): Warn before commands that desync a Frappe Cloud bench - #1728

Open
regdocs wants to merge 2 commits into
developfrom
feat/frappe-cloud-prod-guard
Open

regdocs wants to merge 2 commits into
developfrom
feat/frappe-cloud-prod-guard

Conversation

@regdocs

@regdocs regdocs commented Aug 6, 2026 •

Copy link
Copy Markdown
Member

Problem

On Frappe Cloud, the bench inside the container is a replica, not the source of truth. Press owns the Bench and Site records and rebuilds the container from an image on every deploy. Commands run by hand over SSH drift the two apart, and the drift is silent:

  • bench --site x install-app erpnext — the dashboard still shows the old app list, and the app is gone after the next deploy
  • bench --site x drop-site — Press keeps showing the site as active, and keeps billing it
  • bench get-app / bench update — discarded on the next deploy, and the recorded app hashes no longer match
  • bench set-config / bench config — overwritten on the next config push

Each of these turns into a support ticket that starts with "the dashboard is wrong".

What this does

Prompts before those commands and explains which record goes out of sync:

This bench is managed by Frappe Cloud.

  $ bench --site foo.frappe.cloud install-app erpnext

Frappe Cloud tracks installed apps on the Site record. The app will be
missing from the dashboard and will not survive the next deploy.

Use the Frappe Cloud dashboard instead.

Run it anyway? [y/N]:

Declining exits non-zero. Accepting runs the command — the guard is advisory, not a lock.

When it fires

All of these must hold, so it's inert everywhere except an interactive shell on a managed bench:

Condition Why
Command is in GUARDED_COMMANDS, or is an alias of one several entries; backup, console, mariadb, build, doctor, clear-cache etc. are untouched
frappe_cloud in common_site_config (or FRAPPE_CLOUD env) Set by Press — see frappe/press companion PR below. Self-hosted benches never see this
sys.stdin.isatty() The agent drives benches over docker exec with no -t, so automation passes through untouched
BENCH_SKIP_PROD_GUARD unset Escape hatch for scripted interactive use
No --help / -h in argv bench update --help shouldn't prompt for a command it never runs

Where the hook lives

In cli(), after the --help/no-args early return and before both dispatch paths. This placement is load-bearing: commands not registered with bench (everything --site-scoped) are handed to frappe via os.execv, which replaces the process. Anything downstream of that can't prompt.

Guarded commands

Site state (tracked on the Site record): new-site, drop-site, install-app, uninstall-app, set-config, remove-config, restore, reinstall, migrate, rename, set-admin-password

Bench state (tracked on the Bench record / Release Group): get-app, new-app, remove-app, update, switch-to-branch, switch-to-develop, retry-upgrade, pip, setup, config, start, restart, drop

Aliases are resolved from the command registry, not listed in the table: MultiCommandGroup files one command object under several names, so bench get (= get-app) and bench remove / bench rm (= remove-app) are covered, and an alias added to a guarded command later needs no table edit. A test walks the registry and fails if any alias of a guarded command is unguarded.

Notes

No --yes flag. frappe's reinstall already defines its own --yes, and unregistered commands are execv'd to frappe verbatim — a bench-level flag would either collide or have to be stripped from argv, changing what bench --site x reinstall --yes means. BENCH_SKIP_PROD_GUARD=1 is the bypass instead, and the abort message names it.

No agent changes needed. Verified that agent/bench.py builds docker exec without -t, so agent-issued commands are non-interactive and skip the prompt.

Testing

15 unit tests in bench/tests/test_prod_guard.py cover detection, the command table, and every skip path.

Manually against a real bench:

  • TTY + FRAPPE_CLOUD=1 + install-app, declined → exit 1, never reached frappe
  • Same command non-interactively → no prompt, passed straight through to frappe
  • TTY + install-app --help → help printed, no prompt

Companion PR

Requires frappe/press#7130 to write the marker. This PR is a no-op without it.

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported alias bypass is fixed and covered by regression tests.

Reviews (4): Last reviewed commit: "fix(cli): Guard command aliases, not jus..." | Re-trigger Greptile

Comment thread bench/utils/prod_guard.py Resolved
On Frappe Cloud, Press owns the Bench and Site records and rebuilds the
container from an image on every deploy, so commands run by hand inside
the container silently drift the two apart.

Prompt before those commands when the bench's common_site_config carries
the frappe_cloud marker. The check runs before frappe passthrough
commands are execv'd, since the process is replaced there, and skips
when stdin isn't a TTY so the agent's docker exec calls are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MultiCommandGroup files one command object under several names, so
`bench get`, `bench remove` and `bench rm` reach get-app and remove-app
without matching the guard table. Those are exactly the app changes the
table exists to catch, and they were passing through unwarned.

Resolve aliases by identity against the registered commands rather than
listing them, so an alias added later is covered without a table edit.

Point at the dashboard docs while we're here: each entry is now a
(reason, docs_url) pair -- get_guard_reason becomes get_guard -- and the
warning prints the page that documents doing the same thing from the
Frappe Cloud dashboard. `setup` has no dashboard equivalent, so its link
is None and the line is omitted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@regdocs
regdocs force-pushed the feat/frappe-cloud-prod-guard branch from 86b5690 to fb33a7c Compare August 11, 2026 07:04
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant