datawhalechina/hello-agents · error · RuntimeError

IMAP 连接失败: {e}

Error message

IMAP 连接失败: {e}

What it means

RuntimeError('IMAP 连接失败: {e}') is the wrapper run() raises after catching imaplib.IMAP4.error in _fetch_via_imap(); the original exception is chained via 'raise ... from e'. imaplib.IMAP4.error covers all IMAP-level failures: authentication rejection (login errors are IMAP4.error subclasses), command aborts, and connection-timeout variants (IMAP4.abort). Note the f-string is literal in this path only if the source lacks the f prefix — per the source it is f-stringed, so the underlying message is embedded. Because login() failures raise IMAP4.error, wrong credentials typically surface here.

Source

Thrown at Co-creation-projects/Johnx-w-EmailDigestAgent/main.ipynb:345

    "                    \"body\": _parse_email_body(msg)\n",
    "                })\n",
    "            except Exception as e:\n",
    "                console.print(f\"[yellow]  ⚠️ 邮件解析失败: {e}[/yellow]\")\n",
    "                continue\n",
    "\n",
    "        mail.logout()\n",
    "        console.print(f\"[dim]  已断开连接[/dim]\")\n",
    "        return emails\n",
    "\n",
    "    def run(self, hours: int = 24, max_emails: int = 50) -> str:\n",
    "        if self.use_demo:\n",
    "            emails = self.demo_emails[:max_emails]\n",
    "        else:\n",
    "            try:\n",
    "                emails = self._fetch_via_imap(max_emails=max_emails, hours=hours)\n",
    "            except imaplib.IMAP4.error as e:\n",
    "                console.print(f\"[red]❌ IMAP 连接失败: {e}[/red]\")\n",
    "                raise RuntimeError(f\"IMAP 连接失败: {e}\") from e\n",
    "            except ValueError:\n",
    "                raise\n",
    "            except Exception as e:\n",
    "                console.print(f\"[red]❌ 未知错误: {e}[/red]\")\n",
    "                raise\n",
    "\n",
    "        if not emails:\n",
    "            return json.dumps({\"message\": \"没有新的未读邮件\", \"emails\": []}, ensure_ascii=False, indent=2)\n",
    "        return json.dumps({\"count\": len(emails), \"fetch_time\": datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\"), \"emails\": emails}, ensure_ascii=False, indent=2)\n",
    "\n",
    "    def get_parameters(self):\n",
    "        from hello_agents.tools import ToolParameter\n",
    "        return []\n",
    "\n",
    "print(\"✅ EmailFetchTool defined (IMAP version)\")"
   ]
  },
  {

View on GitHub (pinned to 606a07d341)

Solutions

  1. Read the wrapped message: 'IMAP 连接失败: {e}' includes the server's reason — '[AUTHENTICATIONFAILED]' means credentials, 'abort' means the socket died.
  2. For Chinese providers (QQ/163/126), generate and use the IMAP 授权码 as password and confirm IMAP service is switched on in webmail settings.
  3. Verify network reachability of server:993 (e.g. openssl s_client -connect imap.qq.com:993 or a socket test) from the notebook host.
  4. Distinguish auth vs. transient failure: catch imaplib.IMAP4.abort separately and retry once with backoff; do not retry AUTHENTICATIONFAILED.
  5. For a smoke test without valid credentials, run with use_demo=True.

Example fix

// before
try:
    emails = self._fetch_via_imap(max_emails=max_emails, hours=hours)
except imaplib.IMAP4.error as e:
    console.print(f"[red]❌ IMAP 连接失败: {e}[/red]")
    raise RuntimeError(f"IMAP 连接失败: {e}") from e

// after (classify abort vs auth failure, retry only transient)
try:
    emails = self._fetch_via_imap(max_emails=max_emails, hours=hours)
except imaplib.IMAP4.abort as e:
    time.sleep(2)
    emails = self._fetch_via_imap(max_emails=max_emails, hours=hours)  # one retry
except imaplib.IMAP4.error as e:
    raise RuntimeError(f"IMAP 连接失败(请检查授权码/IMAP开关): {e}") from e
Defensive patterns

Strategy: try-catch

Validate before calling

import socket, ssl

def imap_endpoint_reachable(server: str, port: int, timeout: float = 5.0) -> bool:
    try:
        with socket.create_connection((server, port), timeout=timeout):
            return True
    except OSError:
        return False

# before run():
if not imap_endpoint_reachable(server, 993):
    raise SystemExit(f"无法连接 {server}:993,请检查网络/防火墙")

Try / catch

try:
    result = agent.run(hours=24, max_emails=50)
except RuntimeError as e:
    msg = str(e)
    if "AUTHENTICATIONFAILED" in msg or "LOGIN" in msg.upper():
        # credential problem: fix config, do not retry
        print("凭据错误:请更新 IMAP 授权码")
    elif "abort" in msg.lower():
        # transient: safe to retry once after a short delay
        time.sleep(3)
        result = agent.run(hours=24, max_emails=50)
    else:
        raise

Prevention

When it happens

Trigger: agent.run(hours, max_emails) in live mode (use_demo=False) when _fetch_via_imap() raises imaplib.IMAP4.error: mail.login() rejected (bad password / missing authorization code / IMAP disabled for the account), IMAP4.abort from a dropped socket mid-session, or server returning BAD to a command. The except chain deliberately re-raises ValueError (e.g. 未配置邮箱信息) untouched and wraps only IMAP4.error.

Common situations: Using the account password instead of the provider's IMAP authorization code (QQ/163); IMAP not enabled server-side; firewall blocking port 993 outbound; server-side rate limiting after repeated runs; token/session expired for OAuth-based providers.

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/2b92c756dc5875e7. Report an issue: GitHub.