{"record":{"id":"c48030b320cdd121","repo":"odysseus-dev/odysseus","slug":"email-account-not-found-for-selector-account-r","errorCode":null,"errorMessage":"Email account not found for selector {account!r}. Available accounts: {available}","messagePattern":"Email account not found for selector (.+?)\\. Available accounts: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mcp_servers/email_server.py","lineNumber":324,"sourceCode":"            EMAIL_CACHE_DB,\n        ),\n        \"account_id\": None,\n        \"account_name\": None,\n    }\n\n    raw_rows = _read_accounts_from_db()\n    if _mcp_owner_required(raw_rows):\n        raise ValueError(_OWNER_SCOPE_ERROR)\n    rows = _filter_accounts_for_owner(raw_rows)\n    row = _resolve_account_from_rows(rows, account)\n    if _current_owner() and raw_rows and not rows:\n        raise ValueError(\"No email account is configured for the authenticated owner\")\n    if account and rows and not row:\n        available = \", \".join(\n            f\"{r.get('name') or r.get('imap_user')} <{r.get('imap_user') or r.get('from_address') or '?'}>\"\n            for r in rows\n        )\n        raise ValueError(f\"Email account not found for selector {account!r}. Available accounts: {available}\")\n    if row:\n        cfg[\"account_id\"] = row[\"id\"]\n        cfg[\"account_name\"] = row[\"name\"]\n        cfg[\"imap_host\"] = row[\"imap_host\"] or cfg[\"imap_host\"]\n        cfg[\"imap_port\"] = int(row[\"imap_port\"] or cfg[\"imap_port\"])\n        cfg[\"imap_user\"] = row[\"imap_user\"] or cfg[\"imap_user\"]\n        # Passwords in email_accounts are stored encrypted via\n        # src.secret_storage.encrypt — decrypt before handing to IMAP\n        # (same path email_helpers.py:369 uses). Falling back to the raw\n        # ciphertext is what produced AUTHENTICATIONFAILED previously.\n        try:\n            from src.secret_storage import decrypt as _decrypt\n        except Exception:\n            _decrypt = lambda v: v  # noqa: E731\n        cfg[\"imap_password\"] = _decrypt(row[\"imap_password\"]) if row[\"imap_password\"] else cfg[\"imap_password\"]\n        cfg[\"imap_starttls\"] = bool(row[\"imap_starttls\"])\n        # The email_accounts table stores STARTTLS but not an explicit IMAP SSL\n        # flag. Port 993 is implicit TLS for IMAP providers like Gmail.","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/mcp_servers/email_server.py#L306-L342","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the 'Available accounts:' list in the message and use one of those exact selectors","If the account should exist, confirm it in the accounts DB and that its owner matches the authenticated owner","Omit the account argument to let the server resolve the default account"],"exampleFix":"# before\nemails = list_emails(folder='INBOX', account='old-name')\n# after — use a selector from the error's Available accounts list\nemails = list_emails(folder='INBOX', account='main <me@example.com>')","handlingStrategy":"validation","validationCode":"selectors = {r.get('id'), r.get('name'), r.get('imap_user')} | {None}\nif account not in selectors:\n    account = None  # fall back to default resolution","typeGuard":"def is_valid_selector(rows: list[dict], sel: str | None) -> bool:\n    return sel is None or any(sel in (r.get('id'), r.get('name'), r.get('imap_user')) for r in rows)","tryCatchPattern":"try:\n    _load_config(account)\nexcept ValueError as e:\n    if 'not found for selector' in str(e):\n        parse_available_from(e)  # message lists valid selectors\n    raise","preventionTips":["Resolve account selectors dynamically, never hard-code them","Parse the Available accounts list from the error to self-correct","Rename accounts only through the API so dependent scripts can be updated together"],"tags":["email","mcp","validation"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}