odysseus-dev/odysseus · critical · SystemExit
refusing to wipe non-demo target {USER}@{HOST}:{PORT} — set
Error message
refusing to wipe non-demo target {USER}@{HOST}:{PORT} — set DEMO_ALLOW_WIPE=1 to override What it means
SystemExit from scripts/demo_email/seed_demo_emails.py's _wipe(): the IMAP connection parameters are env-overridable (DEMO_IMAP_USER/HOST/PORT), and the target is not recognizably the local demo account (user not ending in @odysseus.local and host not localhost/127.0.0.1/::1) while DEMO_ALLOW_WIPE is unset. The script refuses to expunge every message in every mailbox rather than risk wiping a real account.
Source
Thrown at scripts/demo_email/seed_demo_emails.py:331
def _ensure_mailbox(conn: imaplib.IMAP4, name: str) -> None:
if name.upper() == "INBOX":
return
typ, _ = conn.select(name)
if typ != "OK":
conn.create(name)
def _wipe(conn: imaplib.IMAP4) -> int:
"""Delete every message in every mailbox of this (throwaway) account.
Guard: the connection params are env-overridable, so refuse to run the
destructive expunge unless the target is unmistakably the local demo
account — otherwise a misconfigured DEMO_IMAP_USER/HOST could irreversibly
wipe a real mailbox. Override only with DEMO_ALLOW_WIPE=1 (you must mean it).
"""
safe_target = USER.endswith("@odysseus.local") or HOST in ("localhost", "127.0.0.1", "::1")
if not safe_target and os.getenv("DEMO_ALLOW_WIPE") != "1":
raise SystemExit(
f"refusing to wipe non-demo target {USER}@{HOST}:{PORT} — "
f"set DEMO_ALLOW_WIPE=1 to override")
typ, boxes = conn.list()
n = 0
names = []
if typ == "OK":
for raw in boxes:
line = raw.decode(errors="replace")
# last token, possibly quoted, is the mailbox name
name = line.split(' "/" ')[-1].split(' "." ')[-1].strip().strip('"')
names.append(name)
for name in set(names) | {"INBOX"}:
if conn.select(name)[0] != "OK":
continue
typ, data = conn.search(None, "ALL")
if typ == "OK" and data and data[0]:
ids = data[0].split()
for i in ids:View on GitHub (pinned to f9235ebbf1)
Solutions
- Target the actual local demo account (user ending in @odysseus.local, or host localhost/127.0.0.1/::1)
- If the remote target is genuinely disposable and you accept the data loss, run with DEMO_ALLOW_WIPE=1
- Never point the env vars at a mailbox containing real data
Example fix
# before DEMO_IMAP_USER=real.user@gmail.com DEMO_IMAP_HOST=imap.gmail.com python scripts/demo_email/seed_demo_emails.py # SystemExit: refusing to wipe non-demo target ... # after (throwaway dockerized imap on localhost) DEMO_IMAP_USER=demo@odysseus.local DEMO_IMAP_HOST=127.0.0.1 python scripts/demo_email/seed_demo_emails.py
Defensive patterns
Strategy: validation
Validate before calling
def wipe_target_is_safe(user: str, host: str) -> bool:
return user.endswith("@odysseus.local") or host in ("localhost", "127.0.0.1", "::1")
if not wipe_target_is_safe(DEMO_IMAP_USER, DEMO_IMAP_HOST):
raise ConfigError("refusing wipe of non-demo mailbox; point env at the local demo account") Try / catch
try:
run_seed_script(env)
except SystemExit as e:
if 'refusing to wipe' in str(e):
fix_env_to_local_demo_account() # never auto-set DEMO_ALLOW_WIPE Prevention
- Only ever point DEMO_IMAP_* at the throwaway local demo account
- Treat DEMO_ALLOW_WIPE=1 as a destructive, human-approved action — never script it
- Put the demo account on localhost so the guard passes naturally
When it happens
Trigger: Running the seed script with DEMO_IMAP_USER=real.user@gmail.com DEMO_IMAP_HOST=imap.gmail.com; pointing at a LAN mail server host like 192.168.1.10 (not in the localhost list); any env override where the safety predicate fails and DEMO_ALLOW_WIPE != '1'.
Common situations: Testing the demo flow against a disposable-but-remote mailbox; CI configured with container hostnames (e.g. mail:143) that don't match the safe list; copy-pasting env blocks between environments.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/d370e567b1b2783e.
Report an issue: GitHub.