odysseus-dev/odysseus · error · ValueError

Email account not found for selector {account!r}. Available

Error message

Email account not found for selector {account!r}. Available accounts: {available}

What it means

Raised when an explicit account selector is passed to an email MCP tool and the accounts DB has owner-scoped rows, but _resolve_account_from_rows() finds no match for that selector (id, name, or imap_user). The message enumerates every available account so the caller can immediately pick a valid selector.

Source

Thrown at mcp_servers/email_server.py:324

            EMAIL_CACHE_DB,
        ),
        "account_id": None,
        "account_name": None,
    }

    raw_rows = _read_accounts_from_db()
    if _mcp_owner_required(raw_rows):
        raise ValueError(_OWNER_SCOPE_ERROR)
    rows = _filter_accounts_for_owner(raw_rows)
    row = _resolve_account_from_rows(rows, account)
    if _current_owner() and raw_rows and not rows:
        raise ValueError("No email account is configured for the authenticated owner")
    if account and rows and not row:
        available = ", ".join(
            f"{r.get('name') or r.get('imap_user')} <{r.get('imap_user') or r.get('from_address') or '?'}>"
            for r in rows
        )
        raise ValueError(f"Email account not found for selector {account!r}. Available accounts: {available}")
    if row:
        cfg["account_id"] = row["id"]
        cfg["account_name"] = row["name"]
        cfg["imap_host"] = row["imap_host"] or cfg["imap_host"]
        cfg["imap_port"] = int(row["imap_port"] or cfg["imap_port"])
        cfg["imap_user"] = row["imap_user"] or cfg["imap_user"]
        # Passwords in email_accounts are stored encrypted via
        # src.secret_storage.encrypt — decrypt before handing to IMAP
        # (same path email_helpers.py:369 uses). Falling back to the raw
        # ciphertext is what produced AUTHENTICATIONFAILED previously.
        try:
            from src.secret_storage import decrypt as _decrypt
        except Exception:
            _decrypt = lambda v: v  # noqa: E731
        cfg["imap_password"] = _decrypt(row["imap_password"]) if row["imap_password"] else cfg["imap_password"]
        cfg["imap_starttls"] = bool(row["imap_starttls"])
        # The email_accounts table stores STARTTLS but not an explicit IMAP SSL
        # flag. Port 993 is implicit TLS for IMAP providers like Gmail.

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Read the 'Available accounts:' list in the message and use one of those exact selectors
  2. If the account should exist, confirm it in the accounts DB and that its owner matches the authenticated owner
  3. Omit the account argument to let the server resolve the default account

Example fix

# before
emails = list_emails(folder='INBOX', account='old-name')
# after — use a selector from the error's Available accounts list
emails = list_emails(folder='INBOX', account='main <me@example.com>')
Defensive patterns

Strategy: validation

Validate before calling

selectors = {r.get('id'), r.get('name'), r.get('imap_user')} | {None}
if account not in selectors:
    account = None  # fall back to default resolution

Type guard

def is_valid_selector(rows: list[dict], sel: str | None) -> bool:
    return sel is None or any(sel in (r.get('id'), r.get('name'), r.get('imap_user')) for r in rows)

Try / catch

try:
    _load_config(account)
except ValueError as e:
    if 'not found for selector' in str(e):
        parse_available_from(e)  # message lists valid selectors
    raise

Prevention

When it happens

Trigger: Passing account=<name>, <id>, or <imap_user> that does not match any row visible to the current owner — e.g. a stale account name after the account was renamed or deleted, or a typo in the selector string.

Common situations: Hard-coded account names in scripts that outlive account renames; copy-pasting selectors between environments (dev DB vs prod DB); using an account owned by a different user under multi-owner scoping.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/c48030b320cdd121. Report an issue: GitHub.