name: erispulse-errors-troubleshooting
description: "适用于:遇到异常/组件不工作时的诊断路径:异常总览、排障指南、bug 追踪。覆盖场景:ErisPulse 所有自定義異常都繼承自 ErisPulseError,底層庫異常(aiohttp、aiomysql 等)。本文檔記錄 ErisPulse SDK 的已知 Bug 及其修復情況,按修復版本時間順序排列。"
ErisPulse 异常体系与排障
This skill is auto-built from the ErisPulse documentation. Repository: https://github.com/ErisPulse/ErisPulse Source paths are annotated per section; see docs/ in the repository for the latest content.
内容大纲
AI 按需读取:只读需要的章节即可,不必全文加载。
- 异常捕获总览: 31-47 行
- 異常總覽: 49-77 行
- 結構化屬性: 79-104 行
- 各異常說明與發生位置: 106-215 行
- 捕獲建議: 217-230 行
- 關閉噪音折疊: 232-240 行
- 相關文件: 242-249 行
- 已知问题追踪: 251-266 行
- 字段說明: 268-291 行
- 严重性分级: 293-301 行
- 类型分类: 303-317 行
- 条目模板: 319-342 行
- 統計概覽: 344-368 行
- 已修復的錯誤: 370-1646 行
异常捕获总览
来源:
docs/zh-TW/advanced/errors.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/zh-TW/advanced/errors.md)
異常體系與捕獲指南
Note
L1 核心必讀——首頁「閱讀路徑」中的五篇核心文件之一,模組開發必備。
ErisPulse 所有自定義異常都繼承自 ErisPulseError,底層庫異常(aiohttp、aiomysql 等)
會在框架內部被捕獲並轉換為對應的 ErisPulse 異常——業務程式碼無需依賴任何底層庫異常類型。
{!--< tips >!--}
- 想寬泛兜底:捕獲
ErisPulseError(所有框架異常的基類) - 想精確處理:按模組捕獲(如
ModuleCallTimeoutError、StorageUnreachableError) - 儲存操作預設不拋出:失敗時記錄日誌並返回
False / None / default;連線狀態變化訂閱storage.unreachable/storage.recovered事件 {!--< /tips >!--}
異常總覽
ErisPulseError # 所有框架異常的基類
├── ClientError # HTTP/WS 客戶端請求異常基類(Core/client)
│ ├── ClientConnectionError # 連接層錯誤:DNS 解析失敗、連接被拒絕、網路不可達
│ ├── ClientTimeoutError # 請求超時
│ └── HTTPStatusError # HTTP 狀態碼錯誤(如 4xx/5xx 且 raise_for_status)
├── WebSocketError # WebSocket 異常基類(Core/client 的 WS 連接)
│ └── WebSocketDisconnect # WebSocket 斷開連接(服務端/客戶端通用)
├── ConnectionRegistryError # 連接註冊表異常基類(Core/connections,2.10+)
│ ├── ConnectionNotFoundError # 連接不存在/已斷開註銷(connections.get/close)
│ └── ConnectionPermissionError # 非 owner 關閉他人連接(關閉權歸創建者)
├── StorageError # 存儲異常基類(Core/storage)
│ └── StorageUnreachableError # 存儲後端不可達(建池重試耗盡:資料庫不可達/憑證錯誤)
├── InteractionError # 交互會話異常基類(Core/Event/interaction)
│ ├── InteractionCancelled # 掛起的等待/租約被取消(wait_reply 上層轉為返回 None)
│ └── SessionOccupiedError # 會話互斥租約被佔用(hold() 獲取失敗)
├── ModuleError # 模組系統異常基類(Core/module)
│ └── ModuleCallError # 模組間呼叫異常基類
│ ├── ModuleNotAvailableError # 目標模組未註冊/未啟用/初始化失敗(含懶加載訪問)
│ ├── ServiceNotProvidedError # 目標模組未聲明該服務(meta.services 白名單外)
│ └── ModuleCallTimeoutError # 被調方法執行超時(預設 30s)
├── ShadowError # 影子模組異常基類(Core/shadow)
│ ├── ShadowStateError # 狀態不滿足:目標未加載/已有活躍影子/未綁定影子(start/dismiss)
│ ├── ShadowSourceError # 影子源不可用:路徑不存在/加載器缺失/裝載失敗(start)
│ └── ShadowPromoteError # 轉正失敗:影子未註冊/未加載/加載器無快照/重載失敗(promote)
└── StrictModeError # 嚴格模式致命違規(中止啟動流程,loaders/strict)
結構化屬性
捕獲異常後可讀取結構化屬性(無需解析訊息文字):
| 異常 | 屬性 |
|---|---|
ClientError(含子類) |
.url 請求 URL、.method 請求方法、.attempts 已嘗試次數(重試耗盡時) |
HTTPStatusError |
.status 狀態碼、.message 回應訊息 |
WebSocketDisconnect |
.code 關閉碼、.reason 關閉原因 |
ConnectionNotFoundError |
.connection_id 查找的連線 id |
ConnectionPermissionError |
.connection_id 目標連線 id、.owner 連線歸屬 owner |
StorageUnreachableError |
.backend 後端名(sqlite/mysql/postgres)、.cooldown 冷卻秒數 |
ModuleCallError(含子類) |
.module 目標模組名、.method 目標方法名 |
ModuleCallTimeoutError |
繼承 .module/.method,另有 .timeout 超時時限(秒) |
InteractionCancelled |
.reason 取消原因、.wait_key 會話鍵 |
SessionOccupiedError |
.wait_key 會話鍵、.owner 占用者 |
StrictModeError |
.violations 違規記錄列表 |
from ErisPulse.Core.Bases.errors import ClientError
try:
resp = await sdk.client.post(url, json=payload)
except ClientError as e:
print(f"請求失敗 {e.method} {e.url},共嘗試 {e.attempts} 次: {e}")
各異常說明與發生位置
Client 系列 — Core/client.py / Core/Bases/client.py
sdk.client / HTTP 客戶端與 WebSocket 客戶端發起請求時拋出:
| 異常 | 發生位置 | 典型場景 |
|---|---|---|
ClientError |
請求封裝層 | 其它客戶端錯誤(底層 aiohttp 異常已轉換) |
ClientConnectionError |
連接建立階段 | 目標服務不可達、DNS 失敗、連接被拒 |
ClientTimeoutError |
請求執行階段 | 超過請求超時時間 |
HTTPStatusError |
raise_for_status() |
回應狀態碼為 4xx/5xx |
from ErisPulse.Core.Bases.errors import ClientTimeoutError
try:
resp = await sdk.client.get("https://api.example.com", timeout=5)
except ClientTimeoutError:
...
WebSocket 系列 — Core/client.py(send / receive)
| 異常 | 發生位置 | 典型場景 |
|---|---|---|
WebSocketError |
WS 收發方法 | 連接已關閉、收到意外訊息類型、底層 WS 異常 |
WebSocketDisconnect |
WS 收發方法 | 對端正常斷開連接(框架會自動重連) |
連接註冊表系列 — Core/connections.py(2.10+)
| 異常 | 發生位置 | 典型場景 |
|---|---|---|
ConnectionNotFoundError |
connections.get / assign / dismiss / close |
連接 id 不存在或已斷開註銷 |
ConnectionPermissionError |
非 owner 調用 conn.close() |
跨模組關閉他人連接(發送與分組不受限) |
Storage 系列 — Core/storage / Core/Bases/sql_base.py
| 異常 | 發生位置 | 典型場景 |
|---|---|---|
StorageError |
存儲層 | 存儲相關異常基類 |
StorageUnreachableError |
建池階段 | 資料庫不可達 / 憑證錯誤 / 網路隔離,重試耗盡 |
儲存操作的失敗語義:KV 與查詢操作預設不拋出——失敗時記錄 ERROR 日誌並 回傳
False/None/default(避免連接問題阻塞框架運行)。因此業務程式碼通常 不會捕獲到StorageUnreachableError(它主要供直接操作儲存底層或自訂後端使用)。 運行時感知連接狀態請訂閱生命週期事件storage.unreachable/storage.recovered(詳見lifecycle.md#儲存連接狀態)。連接失敗行為詳見儲存後端 → 連接失敗行為。
Interaction — Core/Event/interaction.py
wait_reply / 會話租約 / 提醒定時器相關:
| 異常 | 發生位置 | 典型場景 |
|---|---|---|
InteractionError |
交互會話層 | 交互會話異常基類 |
InteractionCancelled |
掛起等待被取消時 | 設置到等待 future 上(等待方可捕獲獲取 .reason) |
SessionOccupiedError |
hold() 租約獲取失敗 |
會話已被其他 owner 占用(.owner 可查占用者) |
等待被取消(模組卸載 / 平台關閉 / 同會話新等待取代)時,wait_reply 回傳 None
而不拋出異常(InteractionCancelled 在內部被轉換);需要區分取消原因時才直接捕獲它。
Module 系列 — Core/module.py(sdk.module.call)
| 異常 | 發生位置 | 典型場景 |
|---|---|---|
ModuleError |
模組系統 | 模組系統異常基類 |
ModuleCallError |
module.call() |
模組間呼叫異常基類 |
ModuleNotAvailableError |
module.call() / 慢加載屬性存取 |
目標未註冊 / 未啟用 / 初始化失敗 |
ServiceNotProvidedError |
module.call() |
目標 meta.services 白名單未聲明該方法 |
ModuleCallTimeoutError |
module.call() |
被呼叫協程超過超時時間(預設 30s) |
"目標模組不可用"在不同存取路徑下的異常類型:
| 存取路徑 | 異常 |
|---|---|
await sdk.module.call("X", "method") |
ModuleNotAvailableError(類型化) |
sdk.module.X.attr(慢加載屬性存取,初始化失敗後) |
ModuleNotAvailableError |
sdk.module.X(模組未啟用時的屬性存取) |
AttributeError(Python 屬性慣例,hasattr 依賴此語義) |
from ErisPulse.Core.Bases.errors import ModuleNotAvailableError, ServiceNotProvidedError
try:
history = await sdk.module.call("Chat", "get_history", session_id, n=20)
except ModuleNotAvailableError:
... # 目標模組不存在 / 未啟用
except ServiceNotProvidedError:
... # 目標模組未提供該服務
Shadow 系列 — Core/shadow.py(影子模組)
| 異常 | 發生位置 | 典型場景 |
|---|---|---|
ShadowError |
影子模組機制 | 影子異常基類(統一捕獲所有影子錯誤) |
ShadowStateError |
shadow_start() / dismiss_shadow() |
目標未加載 / 已有活躍影子 / 未綁定影子 |
ShadowSourceError |
shadow_start() |
源路徑不存在 / 插件加載器缺失 / 源無可加載類 / 裝載失敗 |
ShadowPromoteError |
promote_shadow() |
影子未註冊 / 未加載 / 加載器不支援快照 / 轉正重載失敗 |
框架內部參數校驗(ValueError)
儲存查詢建構器的參數校驗(空欄位類型、Insert 非 dict、不安全欄位類型等)拋標準
ValueError——這類屬於開發期編碼錯誤,正常業務程式碼不應捕獲,而應修正呼叫。
適配器標準動作失敗不拋異常:回傳帶 retcode 的回應字典(協定語義,如
retcode=10002 表示動作未實現)——與 client 層"失敗拋 ClientError"是兩條平行
的錯誤通道,適配器開發時需同時處理。
捕獲建議
from ErisPulse.Core import ErisPulseError # 基類已從 Core 聚合導出
try:
...
except ErisPulseError as e:
... # 統一兜底:所有框架自定義異常
- 模組開發:按需精確捕獲(上表),最外層可用
ErisPulseError兜底 - 異常均已從
ErisPulse.Core聚合導出(含SessionOccupiedError/InteractionCancelled/StrictModeError),也可從ErisPulse.Core.Bases.errors導入 - 完整定義見
src/ErisPulse/Core/Bases/errors.py
關閉噪音折疊
長期駐留進程在關閉(或背景循環被取消)時,事件循環常會刷出一批無處理價值的
Task was destroyed but it is pending! 類解釋器噪音。框架內建同類噪音折疊:
2 秒視窗內的同類訊息降級為 TRACE 並合併計數,只輸出一次,結尾帶
「(另有 N 次同類訊息被折疊)」後綴。這是刻意的降噪行為——看到折疊後綴不代表
框架吞掉了真實異常,業務異常(上表各類)照常完整輸出。折疊僅針對解釋器退出噪音,
業務異常不受影響。什麼級別的異常值得留痕、什麼可以降級/折疊,遵循
工程健壯性規則 · 異常留痕分級。
相關文件
根據排查意圖選擇:
- 儲存操作返回
False/None,想搞清連接狀態 → 儲存後端 · 連接失敗行為,或訂閱 storage.unreachable / storage.recovered 事件 wait_reply返回None,想知道被什麼取消 → 互動會話系統(取消原因對照表)module.call抛模組呼叫異常 → 模組間通信- 連接 / WebSocket 報錯 → 網路用戶端(ClientError 體系)與連接池與廣播(連接註冊表異常)
已知问题追踪
来源:
docs/zh-TW/bug-tracker.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/zh-TW/bug-tracker.md)
Bug 追蹤器
本文檔記錄 ErisPulse SDK 的已知 Bug 及其修復情況,按修復版本時間順序排列。
寫給讀者 沒有任何軟體天生完美,再細心的開發者也會留下小錯誤。本追蹤收錄的都是對運行有實際影響的問題——那些過於細微、連「輕微」等級都達不到的瑕疵並不會出現在這裡。清單中「嚴重」項看起來不少,但公開記錄這些 Bug 的初衷是讓排查與回溯更順暢,而非製造焦慮:能被看見、被記錄、被修復的問題,本身就是項目不斷變好的證明。看到這份清單不必緊張,它是一份排查工具,而不是恐懼的來源。
如何閱讀 & 維護約定
- 每條 Bug 記錄包含問題描述、根因分析、影響版本範圍、修復方案等結構化字段,建議升級前先檢索「影響版本」是否覆蓋當前使用的版本。
- 如需新增 Bug 條目,請在對應位置補充內容,遵循下文字段規範與嚴重性/類型分類。
字段說明
必填字段
| 字段 | 說明 |
|---|---|
| 問題 | Bug 的外在表現、用戶可觀察到的異常現象。盡量給出報錯資訊或典型場景 |
| 原因 | 根因分析,指向具體的代碼缺陷(含「根因鏈路」圖示用於複雜場景) |
| 影響版本 | 受影響的版本區間,格式 引入版本 - 修復版本(含兩端 dev 版本) |
| 修復版本 | 修復該 Bug 的具體版本號 |
| 修復內容 | 修復方案的簡要描述,含關鍵代碼變更點 |
| 修復日期 | 對應修復版本的發布日期,採用 YYYY/MM/DD 格式 |
| 嚴重性 | 按下文「嚴重性分級」標註 |
| 類型 | 按下文「類型分類」標註,可組合(如 適配器 / 路由) |
可選字段
| 字段 | 說明 | 適用場景 |
|---|---|---|
| 復現步驟 | 觸發該 Bug 的最小可復現路徑 | 複雜 Bug、偶發性 Bug 建議補充 |
| 關聯 | 相關 Issue / PR / Commit 鏈接 | 有外部討論記錄時補充 |
| 回歸測試 | 驗證修復、防止再次回歸的測試用例位置 | 已編寫對應 pytest 用例時補充 |
严重性分级
| 标识 | 级别 | 判定标准 | 典型表现 |
|---|---|---|---|
| 🔴 | 严重 | 导致进程崩溃、数据丢失/损坏、核心功能完全不可用、安全漏洞 | OOM Kill、消息无法发送、模块无法加载、热重载失败 |
| 🟡 | 中等 | 功能异常但有规避路径、非核心功能失效、偶发问题 | 状态判断错误、重复触发、缓存过期、错误提示不准 |
| 🟢 | 轻微 | 不影响核心功能、仅代码质量或体验问题、潜在风险未爆发 | 弃用 API、死代码、缺失 warning 日志 |
类型分类
| 类型 | 覆盖范围 |
|---|---|
| 配置系统 | ConfigManager、配置读写、配置 Schema、热更新 |
| 事件系统 | Event 模块(command/message/notice/request/meta)、事件分发、处理器注册 |
| 适配器 | AdapterManager、BaseAdapter、账户解析、Bot 状态、中间件 |
| 路由 | RouterManager、HTTP/WebSocket/SSE 路由、限流、CORS |
| 客户端 | HttpClient、ClientWebSocket、aiohttp 封装 |
| 存储 | StorageManager、SQLite、SQL 构建器、嵌套键 |
| 加载系统 | Loader、LazyModule、ModuleInitializer、严格模式、模块发现 |
| CLI | epsdk 命令、init/run/install、参数解析、信号处理 |
| 运行时 | sdk.run/restart/uninit、生命周期、信号、子进程 |
条目模板
新增 Bug 条目请遵循以下格式:
### [BUG-XXX] 标题
**问题**: 问题描述(报错信息或典型现象)
**原因**: 根因分析
**影响版本**: 引入版本 - 修复版本
**修复版本**: x.x.x
**修复内容**: 修复方案
**修复日期**: YYYY/MM/DD
<!-- 可选字段 -->
**复现步骤**: (复杂 Bug 建议补充)
**关联**: (Issue/PR 链接)
**回归测试**: (验证用例路径)
**严重性**: 🔴 严重 | 🟡 中等 | 🟢 轻微
**类型**: 配置系统 / 事件系统 / 适配器 / 路由 / 客户端 / 存储 / 加载系统 / CLI / 运行时
統計概覽
| 嚴重性 | 數量 |
|---|---|
| 🔴 嚴重 | 16 |
| 🟡 中等 | 26 |
| 🟢 輕微 | 3 |
| 合計 | 45 |
| 類型 | 數量 |
|---|---|
| 事件系統 | 12 |
| 配置系統 | 12 |
| 適配器 | 7 |
| CLI | 3 |
| 儲存 | 3 |
| 加載系統 | 4 |
| 路由 | 2 |
| 客戶端 | 1 |
| 運行時 | 1 |
注:單條 Bug 可歸屬多個類型,上表按主類型統計。
注:BUG-028 / BUG-031 編號空缺(登記時廢棄,為保持既有編號穩定不回收重排)。
已修復的錯誤
[BUG-001] 事件處理器重複註冊導致事件被多次處理
問題: 使用多個 @message / @notice 等裝飾器註冊處理器時,同一事件會被重複觸發多次,導致命令被執行多遍、日誌重複輸出。
原因: BaseEventHandler 向適配器事件總線註冊處理器時缺少去重邏輯,每個裝飾器都會向總線掛載一次,事件分發時被多次調用。
影響版本: 2.2.0-dev.0 - 2.2.1-dev.0
修復版本: 2.2.1-dev.0
修復內容: 優化 BaseEventHandler,確保每個事件類型只向適配器註冊一次處理器,避免重複觸發。
修復日期: 2025/08/18
嚴重性: 🔴 嚴重
類型: 事件系統
[BUG-002] Init 命令適配器配置路徑類型錯誤
問題: 使用 ep init 命令進行交互式初始化時,選擇配置適配器會出現類型錯誤:
交互式初始化失敗: 不支援的運算子類型 (str 和 str)
原因: 2.3.7 版本調整配置文件路徑時,方法參數類型不一致。_configure_adapters_interactive_sync 接收 str 類型參數,但內部使用 Path 的 / 操作符拼接路徑。
影響版本: 2.3.7 - 2.3.9-dev.1
修復版本: 2.3.9-dev.1
修復內容: 將 _configure_adapters_interactive_sync 方法的參數類型從 str 改為 Path,呼叫時直接傳遞 Path 對象。
修復日期: 2026/03/23
嚴重性: 🟡 中等
類型: CLI
[BUG-003] 重啟後命令事件失效
問題: 呼叫 sdk.restart() 後,透過 @command 註冊的命令無法被觸發,表現為發送命令後機器人無回應。
原因: adapter.shutdown() 清空事件總線後,BaseEventHandler 的 _linked_to_adapter_bus 狀態未重置為 False,導致 _process_event 方法認為已經掛載到適配器總線,跳過重新掛載操作。
影響版本: 2.2.x - 2.4.0-dev.2
修復版本: 2.4.0-dev.3
修復內容: 引入 _linked_to_adapter_bus 狀態追蹤,_clear_handlers() 斷開總線連接後,下次 register() 自動重新掛載,適配 shutdown/restart 場景。
修復日期: 2026/04/09
嚴重性: 🔴 嚴重
類型: 事件系統
[BUG-004] 生命週期事件處理器未清理
問題: sdk.restart() 後,舊的生命週期事件處理器仍然存在並重複觸發,導致同一個事件被多次處理。
原因: lifecycle._handlers 字典在 uninit() 時從未被清理,restart 後舊處理器與新處理器同時存在。
影響版本: 2.3.0 - 2.4.0-dev.2
修復版本: 2.4.0-dev.3
修復內容: 在 Uninitializer 的清理流程末尾(所有事件提交之後),清空 lifecycle._handlers。
修復日期: 2026/04/09
嚴重性: 🟡 中等
類型: 運行時
[BUG-005] Event.is_friend_add/is_friend_delete 的 detail_type 與 OB12 標準不一致
問題: Event.is_friend_add() 檢查 detail_type == "friend_add",Event.is_friend_delete() 檢查 detail_type == "friend_delete",但 OneBot12 標準定義的 detail_type 值為 "friend_increase" 和 "friend_decrease"。與 notice.py 中 on_friend_add/on_friend_remove 裝飾器使用的值不一致,導致透過裝飾器註冊的處理器觸發時,對應的 is_friend_add()/is_friend_delete() 判斷方法返回 False。
原因: wrapper.py 中使用了非標準的命名,而 notice.py 使用了正確的 OB12 標準命名。
影響版本: 實裝至今
修復版本: 2.4.2-dev.1
修復內容: 將 is_friend_add() 的匹配值從 "friend_add" 改為 "friend_increase",is_friend_delete() 從 "friend_delete" 改為 "friend_decrease"。
修復日期: 2026/04/13
嚴重性: 🟡 中等
類型: 事件系統
[BUG-006] adapter.clear() 未清理 _started_instances 導致重啟後狀態不正確
問題: AdapterManager.clear() 方法清除了 _adapters、_adapter_info、處理器和 _bots,但遺漏了 _started_instances 集合。如果適配器正在運行時呼叫 clear(),_started_instances 會保留懸空引用,導致重啟後狀態判斷錯誤。
原因: 2.4.0-dev.1 引入 _started_instances 時未在 clear() 中同步清理。
影響版本: 2.4.0-dev.1 - 2.4.2-dev.0
修復版本: 2.4.2-dev.1
修復內容: 在 clear() 方法中添加 self._started_instances.clear()。
修復日期: 2026/04/13
嚴重性: 🟡 中等
類型: 適配器
[BUG-007] command.wait_reply() 使用已棄用的 asyncio.get_event_loop()
問題: CommandHandler.wait_reply() 方法使用 asyncio.get_event_loop() 創建 future 和獲取時間戳,該方法在 Python 3.10+ 中已棄用,在異步上下文中應使用 asyncio.get_running_loop()。與同文件中 wrapper.py 的 wait_for() 方法使用的 get_running_loop() 不一致。
原因: 開發時使用了舊版 API,後續新增的 wait_for() 使用了正確的 API 但未回溯修復舊代碼。
影響版本: 2.3.0-dev.0
修復版本: 2.4.2-dev.1
修復內容: 將 command.py 中兩處 asyncio.get_event_loop() 替換為 asyncio.get_running_loop()。
修復日期: 2026/04/13
嚴重性: 🟢 輕微
類型: 事件系統
[BUG-008] Bot 離線事件在 shutdown 過程中被重複提交
問題: 呼叫 adapter.shutdown() 關閉所有適配器時,_update_bot_status() 會在關閉流程中反覆提交 Bot 離線事件,導致同一批 Bot 被多次標記離線並觸發多次 adapter.bot.offline 生命週期事件。
原因: 2.4.0-dev.1 引入的 Bot 狀態追蹤系統未在 shutdown() 期間設置"正在關閉"標誌,_update_bot_status() 無法區分正常離線與關閉流程中的級聯離線。
影響版本: 2.4.0-dev.1 - 2.4.2-dev.1
修復版本: 2.4.2-dev.1
修復內容: 在 AdapterManager 中新增 _is_being_shutdown 標誌,shutdown() 開始時置為 True、結束時清除;_update_bot_status() 檢查該標誌後跳過關閉過程中的重複提交。
修復日期: 2026/04/21
嚴重性: 🟡 中等
類型: 適配器
[BUG-009] LazyModule 同步訪問 BaseModule 導致未初始化完成
問題: 用戶在同步上下文中訪問懶加載的 BaseModule 屬性時,模組使用 loop.create_task() 異步初始化但不等待,導致屬性訪問時可能未初始化完成,引發競態條件。
原因: _ensure_initialized() 對 BaseModule 使用 loop.create_task(self._initialize()) 後立即返回,未確保初始化完成。
影響版本: 2.4.0-dev.0 - 2.4.2-dev.1
修復版本: 2.4.2-dev.2
修復內容: 在同步上下文中,BaseModule 的初始化改為使用 asyncio.run(self._initialize()),確保初始化完成後再返回。保持透明代理特性,用戶無需感知同步/異步差異。
修復日期: 2026/04/21
嚴重性: 🟡 中等
類型: 加載系統
[BUG-010] 配置系統多線程寫入導致數據丟失
問題: 在多線程環境下,多個線程同時呼叫 config.setConfig() 時,_flush_config() 讀取-修改-寫入操作不是原子性的,可能導致部分寫入丟失。
原因: _flush_config() 雖然使用了 RLock,但檔案讀取和寫入之間沒有檔案鎖保護,且 _schedule_write 的 Timer 可能被多次觸發導致覆蓋。
影響版本: 2.3.0 - 2.4.2-dev.1
修復版本: 2.4.2-dev.2
修復內容:
- 添加檔案鎖機制(
_file_lock)確保檔案操作原子性 - 使用暫存檔案寫入後原子性重命名(
os.replace/os.rename) - 改進
_schedule_write的 Timer 取消和重新調度邏輯
修復日期: 2026/04/21
嚴重性: 🔴 嚴重
類型: 配置系統
[BUG-011] Windows 下 CTRL+C 無法停止程序
問題: 在 Windows 上直接運行 python main.py 時,按下 CTRL+C 無法終止程序。程序正常啟動並輸出路由伺服器資訊後,CTRL+C 完全無反應,只能透過任務管理器強殺程序。而透過 epsdk run 啟動時可以正常停止——但 epsdk run 是透過子進程模型運行的。
原因: Hypercorn ASGI 伺服器的 serve() 函數內部透過 signal.signal(SIGINT, handler) 註冊了自己的 SIGINT 處理器,覆蓋了 Python 預設的 KeyboardInterrupt 處理機制。當透過 asyncio.create_task() 啟動 Hypercorn 作為背景任務時,Hypercorn 的內部 shutdown 流程無法正常觸發(因為它期望的是 worker_serve 模式),導致 CTRL+C 信號被 Hypercorn 吞掉但不會引發任何清理動作。
影響版本: 2.3.6 - 2.4.2
修復版本: 2.4.3-dev.0
修復內容:
- 將 ASGI 伺服器從 Hypercorn 切換為 Uvicorn(
pyproject.toml依賴變更) - 使用
uvicorn.Server._serve()直接啟動伺服器,繞過capture_signals()信號處理上下文管理器 - 透過
server.should_exit = True實現優雅停止,超時則取消背景任務 - 同步移除子進程運行模型和
runtime/cleanup.py清理模組(子進程清理機制不再需要)
修復日期: 2026/04/28
嚴重性: 🔴 嚴重
類型: CLI / 運行時
[BUG-012] 熱重啟後已更新模組的 Python 代碼未生效
問題: 執行 sdk.restart() 軟重啟後,已透過 epsdk install 升級的模組/適配器的新代碼(如新增 API 路由)不生效,仍運行舊版本邏輯。必須完全重啟進程才能加載最新代碼。
原因: _do_restart() 在重新初始化時呼叫 entry_point.load(),但該函數從 sys.modules 返回了緩存的舊版本模組物件,而非從磁盤重新加載。
影響版本: 早期版本 - 2.4.3-dev.1
修復版本: 2.4.3-dev.1
修復內容: 在 uninit() 後、init() 前清理 sys.modules 中已加載模組/適配器包的緩存,使 entry_point.load() 從磁盤加載最新代碼。新增 _collect_top_level_modules() 與 _invalidate_module_cache() 輔助方法,透過 top_level.txt 或 entry-point value 推導頂層模組名。
修復日期: 2026/05/03
嚴重性: 🔴 嚴重
類型: 加載系統 / 運行時
[BUG-013] 模組加載策略排序邏輯錯誤
問題: ModuleLoadStrategy 提供了 priority 字段用於聲明模組的初始化優先級,但加載策略的實現存在失誤,導致模組未按預期的優先級順序初始化,實際按 entry_points() 的預設順序加載。當模組間存在加載依賴時,無法透過 priority 確保正確的初始化先後關係。
原因: 加載策略的實現中排序邏輯有誤,initialize_modules() 未使用 priority 對模組列表進行排序。
影響版本: 2.3.4 - 2.4.5-dev.2
修復版本: 2.4.5-dev.3
修復內容: 在 initialize_modules() 遍歷前,按 priority 降序排序模組列表。同 priority 的模組保持原有相對順序(穩定排序)。
修復日期: 2026/05/15
嚴重性: 🟡 中等
類型: 加載系統
[BUG-014] 適配器中間件返回 None 導致事件數據丟失
問題: adapter.emit() 在執行 OneBot12 中間件鏈時,如果某個中間件返回 None(例如忘記 return data),後續中間件和所有事件處理器收到的 processed_data 變為 None,導致事件處理完全失效。
原因: 中間件鏈的實現 processed_data = await middleware(processed_data) 未檢查返回值是否為 None,直接覆蓋了上一步的處理結果。
影響版本: unknown - 2.4.5-dev.3
修復版本: 2.4.5-dev.4
修復內容: 中間件返回 None 時忽略該返回值,保留原數據繼續傳遞,並輸出 warning 級別日誌。
修復日期: 2026/05/15
嚴重性: 🔴 嚴重
類型: 適配器 / 事件系統
[BUG-015] 配置檔案路徑依賴工作目錄
問題: ConfigManager 的配置檔案路徑預設為相對路徑 "config/config.toml",在運行時依賴 os.getcwd() 解析。如果工作目錄在運行期間發生變化(例如透過 os.chdir()),配置檔案的讀寫操作會指向錯誤的位置,導致配置丟失或讀取到舊數據。
原因: __init__ 中直接儲存相對路徑,未在初始化時將其解析為絕對路徑。
影響版本: 2.3.7 - 2.4.5-dev.3
修復版本: 2.4.5-dev.4
修復內容: 在 ConfigManager.__init__() 中,如果傳入的路徑為相對路徑,自動透過 os.path.abspath() 解析為絕對路徑。
修復日期: 2026/05/15
嚴重性: 🟡 中等
類型: 配置系統
[BUG-016] BaseStorage 將儲存值 None 與鍵不存在混淆
問題: BaseStorage.get_multi() / __getattr__() 無法區分"鍵不存在"與"鍵的值就是 None"兩種情況,用戶顯式存入 None 後再讀取時會被當作鍵不存在處理。
原因: 取值邏輯直接用 value is None 判斷鍵是否存在,缺少獨立的"缺失"標記。
影響版本: 早期版本 - 2.4.6-dev.6
修復版本: 2.4.6-dev.6
修復內容: 引入 _SENTINEL 哨兵值區分"鍵不存在"與"值為 None",二者不再混淆。
修復日期: 2026/06/07
嚴重性: 🟡 中等
類型: 存儲
[BUG-017] WebSocket 路由 auto_accept 標誌在服務重啟後丟失
問題: 服務重啟(如 sdk.restart())後,所有 WebSocket 路由的 auto_accept 配置都變回 False,原本期望自動 accept 的連接變為掛起狀態,客戶端長時間收不到回應,表現為 WS 連接卡死。
原因: _restore_routes_from_records() 在從持久化記錄恢復路由時把 auto_accept 硬編碼為 False,未讀取原始記錄中的值;同時路由儲存元組也從二元組擴展為三元組時未同步更新恢復邏輯。
影響版本: 2.3.8-dev.0 - 2.4.6-dev.6
修復版本: 2.4.6-dev.6
修復內容: 路由儲存元組擴展為 (handler, auth_handler, auto_accept),_restore_routes_from_records() 從記錄讀取真實 auto_accept 值而非硬編碼 False。
修復日期: 2026/06/07
嚴重性: 🔴 嚴重
類型: 路由
[BUG-018] HTTP/WS 客戶端併發呼叫導致崩潰與連接泄漏
問題: Core/client.py 的 HTTP 與 WebSocket 客戶端在併發場景下存在多個穩定性缺陷,會導致連接泄漏或進程崩潰:
- 多協程併發呼叫
ClientWebSocket.receive()時 aiohttp 抛出Concurrent call to receive() is not allowed _get_http_session()/_get_ws_session()併發呼叫可能創建多個 session 且_drain_sessions()未關閉舊連接,造成連接泄漏request()的異常捕獲順序錯誤:except ClientConnectionError(ErisPulse 異常)永不觸發,aiohttp 的連接錯誤被通用except Exception接住,導致"連接重試 + session 重建"邏輯(死代碼)從未執行send_json()忽略mode="binary"參數;_get_ws_session()未傳入預設請求頭
原因: 客戶端初次實現(2.4.6-dev.5)缺少併發保護與異常分類,對 aiohttp 異常體系與 ErisPulse 自定義異常的繼承關係處理不當。
影響版本: 2.4.6-dev.5 - 2.4.8
修復版本: 2.4.8
修復內容:
- 新增
_recv_lock序列化所有receive()/receive_text()/receive_bytes()調用 - 新增
_session_lock保護 session 創建;_drain_sessions()改為異步方法並真正關閉舊 session - 重構
request()異常捕獲順序:asyncio.TimeoutError→aiohttp.ClientConnectionError(觸發 session 重建)→aiohttp.ClientError→ClientError(透傳)→Exception - 修復
send_json()的 mode 處理、_get_ws_session()預設請求頭透傳、close()的併發競態、HttpResponse.__aexit__重複release()
修復日期: 2026/06/12
嚴重性: 🔴 嚴重
類型: 客戶端
[BUG-019] 適配器熱重載時路由衝突導致重載失敗
問題: 第三方模組(如 Dashboard)觸發適配器熱重載,或適配器啟動失敗重試時,因上次註冊的舊路由(如 onebot11_default)未清理,拋出 WebSocket路徑 ... 已註冊 衝突,導致重載失敗。需要完全重啟進程才能恢復。
原因: AdapterManager.shutdown() 僅以 unregister_all_by_namespace(platform) 清理路由,但適配器(如 OneBot11)以 onebot11_{account_name} 為命名空間註冊 WS 路由,顆粒度不匹配導致清理為空操作;啟動失敗重試路徑也未清理上次殘留路由。
影響版本: 早期版本 - 2.4.9
修復版本: 2.4.9
修復內容:
- 路由註冊時透過
current_ownerContextVar 自動追蹤owner → namespace歸屬關係 - 新增
unregister_all_by_owner(owner),停止/重啟時同時按 owner 清理,覆蓋細顆粒度命名空間 - 新增
_stop_adapter(platform)原語("停止即清理"),將停止適配器與回收其註冊的資源綁定在一次呼叫裡,restart()和啟動失敗重試均經此入口 - 新增框架級
adapter.restart(platform)API,第三方模組應呼叫此方法而非直接操作適配器實例
修復日期: 2026/06/12
嚴重性: 🔴 嚴重
類型: 適配器 / 路由
[BUG-020] 子進程模式 ep run <script> 找不到腳本所在目錄的子包
問題: 使用 ep r .\main.py 非熱重載模式運行腳本時,如果腳本有相對匯入(如 from qg import ...),會報 No module named 'qg' 錯誤。而 --reload 模式可以正常運行。
原因: 非熱重載模式直接呼叫 runpy.run_path() 執行腳本,該函數不會自動將腳本所在目錄加入 sys.path。而 --reload 模式透過 subprocess.Popen 子進程運行,子進程自動繼承當前工作目錄,sys.path[0] 即為腳本所在目錄,所以能正常工作。
影響版本: 2.5.0 - 2.5.2-dev.0
修復版本: 2.5.2-dev.0
修復內容: 在 runpy.run_path() 呼叫前,手動將腳本所在目錄插入 sys.path[0]。
修復日期: 2026/06/27
嚴重性: 🟡 中等
類型: CLI
[BUG-021] SQL 查詢建構器拒絕合法通配符和列表達式
問題: SQLiteQueryBuilder 的 _build_select_sql() 對所有 SELECT 列呼叫 _validate_identifier(),該函數使用嚴格的白名單正則 ^[a-zA-Z_][a-zA-Z0-9_]*$,導致合法 SQL 語法被誤判為不安全列名:
SELECT *—*是 SQL 標準通配符SELECT COUNT(*)— 聚合函數SELECT users.name— 限定列名SELECT col AS alias— 列別名
其中 Select("*") 被 Cron 等模組使用,導致模組 on_load 執行失敗,模組無法加載。
原因: 2.4.6 版本增強了 SQL 注入防護,引入了 _validate_identifier() 白名單校驗。該校驗應用於所有列名,但未區分讀取端(SELECT/ORDER BY)和寫入端(INSERT/UPDATE)。SELECT 列允許複雜的 SQL 表達式,不應受簡單標識符白名單限制。
影響版本: 2.4.6 - 2.5.2-dev.1
修復版本: 2.5.2-dev.2
修復內容: 將 SELECT/ORDER BY 的列校驗從白名單模式改為黑名單模式:
- 新增
_validate_select_column()函數,僅攔截 SQL 注入危險字元(;'"--/**/\x00換行符) - 允許任意合法 SQL 列表達式(
*、table.*、table.column、COUNT(*)、col AS alias等) - INSERT/UPDATE 列名仍保持嚴格白名單校驗(僅允許簡單標識符)
修復日期: 2026/06/29
嚴重性: 🔴 嚴重
類型: 存儲
[BUG-022] _resolve_account() 賬戶解析回歸(_accounts_data 未填充)
問題: 2.5.2 配置系統重構後,聲明了 AccountConfigClass 的多賬戶適配器在呼叫 wait_reply、reply 等需要發送訊息的方法時,報錯 ValueError("未聲明 AccountConfigClass,無法解析賬戶")。即使適配器正確配置了多賬戶資訊,賬戶解析仍然失敗。
原因: 2.5.2-dev.5 將 _load_accounts()(負責讀取配置 + 校驗 + 填充 _accounts_data)重構為 _ensure_accounts_exist()(僅生成配置範本),但 _resolve_account() 仍檢查 self._accounts_data is None。由於 _ensure_accounts_exist() 不再填充 _accounts_data,該屬性始終為 None,導致 _resolve_account() 提前返回 (None, None),賬戶解析完全失效。
根因鏈路:
_load_accounts() 被刪除
→ __init__ 不再填充 _accounts_data
→ _accounts_data 恆為 None
→ _resolve_account() 檢查 _accounts_data is None → return (None, None)
→ 下游呼叫 _resolve_account 的地方(如 call_api)拿到 None
→ 觸發報錯
影響版本: 2.5.2-dev.5 - 2.5.2
修復版本: 2.5.3
修復內容: 在 BaseAdapter.__init__ 中,_ensure_accounts_exist() 之後恢復 _accounts_data 的填充:
if self.AccountConfigClass is not None:
self._ensure_accounts_exist()
self._accounts_data = self.accounts # 恢復填充,數據源為實時讀取的 accounts 屬性
_resolve_account() 邏輯保持不變,完全向後兼容:
- 不聲明
AccountConfigClass的適配器:_accounts_data保持None→ 返回(None, None) - 聲明了
AccountConfigClass的適配器:_accounts_data被填充 → 正常解析 - 覆寫
_load_accounts或手動設定_accounts_data的適配器:在super().__init__()後覆蓋,優先級最高
修復日期: 2026/07/07
嚴重性: 🔴 嚴重
類型: 適配器 / 配置系統
[BUG-023] 修改賬戶配置後適配器緩存未刷新導致賬戶解析失敗
問題: 用戶透過 Dashboard 修改多賬戶適配器的賬戶配置(如填寫 token)後,適配器仍使用舊緩存,呼叫發送訊息相關方法時報 未找到可用賬戶 (account_id=default)。必須重啟進程才能讓新配置生效。
原因: _accounts_data 僅在 BaseAdapter.__init__ 時從配置儲存讀取一次,之後不再刷新。AdapterManager._run_adapter() 與 restart() 在呼叫 adapter.start() 前未重新讀取賬戶配置,導致緩存與實際配置脫節。
影響版本: 2.4.6 - 2.5.4
修復版本: 2.5.4
修復內容: 在 AdapterManager._run_adapter() 和 restart() 中,呼叫 adapter.start() 之前刷新 adapter._accounts_data = adapter.accounts,確保每次啟動時使用最新配置。
修復日期: 2026/07/09
嚴重性: 🔴 嚴重
類型: 適配器 / 配置系統
[BUG-024] storage.set() 寫入大數字 ID 鍵時觸發 OOM Kill
問題: 呼叫 storage.set() 寫入包含大純數字段(如 QQ 群號 871684833)的嵌套鍵路徑時,進程被容器 OOM Kill(退出碼 -9),服務直接崩潰無法恢復。
原因: _set_nested_value 的遞迴實現中,嵌套鍵路徑裡的純數字段被 isdigit() 誤判為列表索引,觸發 current.extend([None] * (index - len(current) + 1)),試圖分配數億元素的列表,瞬間耗盡記憶體。
根因鏈路:
鍵路徑包含純數字段(如群號 871684833)
→ isdigit() 誤判為陣列索引
→ extend([None] * (871684833 - len(current) + 1))
→ 試圖分配數億元素
→ 記憶體耗盡 → 容器 OOM Kill(退出碼 -9)
影響版本: 2.5.1 - 2.5.5
修復版本: 2.5.5
修復內容:
- 預創建中間層時始終使用字典,不再根據下一段是否為數字猜測容器類型
- 設定最終值時,僅當容器本身已是列表且索引小於
STORAGE_MAX_LIST_INDEX(10000)時才按索引處理,超大索引安全跳過 - 將遞迴實現改為迭代實現,消除原代碼中潛在的無限遞迴風險
- 新增
STORAGE_MAX_LIST_INDEX常量到Core/constants.py,集中管理索引安全上限
修復日期: 2026/07/10
複現步驟:
# 寫入包含大數字段(如 QQ 群號)的嵌套鍵路徑即可觸發
await sdk.storage.aset("groups.871684833.name", "某群")
# → 進程記憶體瞬間飆升,被 OOM Kill
回歸測試: tests/unit/test_unit_storage.py 新增 4 個回歸用例
test_nested_key_numeric_segment_as_dict_key— 精確複現 OOM 場景test_nested_key_numeric_segment_multiple— 多個連續數字段均作為字典鍵test_nested_key_existing_list_index_set_within_limit— 已有列表合理索引寫入test_nested_key_list_index_safety_limit— 超大索引安全限制驗證
嚴重性: 🔴 嚴重
類型: 存儲
[BUG-025] on_config_update 回調未被核心路由
問題: on_config_update(old, new) 回調在基類(BaseModule / BaseAdapter)中已定義,但框架核心未將其與配置變更事件關聯。實際表現:透過配置管理面板改配置時可以觸發,而手動編輯 config.toml 或代碼呼叫 setConfig() 時不會觸發 on_config_update。
原因: ConfigManager 在配置變更時會發射 config.set / config.updated 生命週期事件,但缺少將這些事件轉發到各組件 on_config_update 方法的訂閱邏輯。
根因鏈路:
核心未訂閱 config.set / config.updated
→ 配置變更事件無轉發
→ on_config_update 未被呼叫
→ 手動編輯檔案 / 代碼 setConfig 不觸發熱更新回調
影響版本: 全版本
修復版本: 2.6.2
修復內容: ModuleManager / AdapterManager 訂閱 config.set(覆蓋代碼 setConfig() 路徑)與 config.updated(覆蓋手動編輯檔案路徑)事件訂閱,按配置鍵前綴匹配後呼叫對應組件的 on_config_update,傳入類型安全的配置物件。同時修復 _flush_config() 寫入檔案後未同步 _config_mtime 的問題,避免框架自身寫入被檔案監聽任務誤判為外部修改而重複觸發 config.updated。
相容性說明: 配置熱更新現由框架核心統一維護。此前由配置管理面板代為觸發的邏輯已移除,升級框架後需同步升級配置管理面板,否則會出現重複觸發(核心 + 面板各調一次)。on_config_update 方法簽名與語義保持不變,子類無需修改。
修復日期: 2026/07/23
嚴重性: 🟡 中等
類型: 配置系統
[BUG-026] notice/request 事件 reply 目標推斷錯誤
問題: 在群通知事件(如成員加群 group_member_increase)中呼叫 event.reply(),訊息被發送到觸發事件的使用者私聊,而非事件所在的群。好友通知事件同理,回覆目標可能錯亂。
原因: infer_receive_type() 將事件的 detail_type 直接當作會話類型回傳。對於 message 事件這是正確的(detail_type 值 private/group 即會話類型),但 notice/request 事件的 detail_type 是語義子類型(如 group_member_increase、friend_increase),不是會話類型。後續的 convert_to_send_type() 和 get_id_field() 在映射表中找不到該值,回退到預設的 "user" / "user_id",導致回覆目標錯亂。
根因鏈路:
notice 事件 detail_type="group_member_increase"
→ infer_receive_type() 直接回傳 "group_member_increase"
→ convert_to_send_type("group_member_increase") 不在映射表 → 回退 "user"
→ get_id_field("group_member_increase") 不在映射表 → 回退 "user_id"
→ target_id = event["user_id"] ← 新成員私聊(而非群)
影響版本: 全版本
修復版本: 2.7.0-dev.3
修復內容: infer_receive_type() 增加判斷——detail_type 只有在是已知會話類型(標準類型或自定義類型)時才直接回傳;否則根據 ID 欄位(group_id / channel_id / user_id 等)推斷正確的會話類型。
回歸測試: tests/unit/test_unit_session_type.py → TestNoticeRequestTypeInference(10 用例)
修復日期: 2026/07/29
嚴重性: 🟢 輕微
類型: 事件系統
[BUG-027] 路由限流清理任務使用固定窗口導致長窗口限流規則失效
問題: 將路由限流配置為長窗口規則(如 100/hour、{"requests": 100, "window": 3600})時,限流形同虛設——實際表現近似 100/minute(每小時可放過至約 6000 次請求),完全無法起到預期的小時級防護作用。
原因: _apply_rate_limit 解析得到每路由的實際 window(最高 3600 秒),per-request 檢查也確實使用該窗口;但後台清理任務 _cleanup_expired_rate_limits 卻用固定常量 DEFAULT_RATE_LIMIT_WINDOW_SECS(60 秒)作為所有路由的統一清理閾值。於是 100/hour 路由中早於 60 秒的時間戳被清理任務提前清除,小時窗口內永遠累積不到接近 100 條記錄,限流被嚴重削弱。
根因鏈路:
_apply_rate_limit 解析 window=3600(100/hour)
→ per-request 檢查按 3600s 保留時間戳(正確)
→ 但 _cleanup_expired_rate_limits 用固定 max_window=60s 清理
→ 60s 前的時間戳被全部清除
→ 小時窗口永遠只餘最近 1 分鐘的記錄
→ 100/hour 實際退化為 ~100/minute(寬放約 60 倍)
影響版本: 2.6.0-dev.0 - 2.7.0-dev.4
修復版本: 2.7.0-dev.5
修復內容: 新增 _rate_limit_windows: dict[str, int] 按 store key 記錄每路由實際窗口;_apply_rate_limit 首次創建條目時寫入窗口;_cleanup_expired_rate_limits 改為按各 key 自身窗口清理(缺失時回退預設值);清理刪除條目與 stop() 時同步維護兩個字典。
修復日期: 2026/07/31
回歸測試: tests/unit/test_unit_router.py → TestRateLimit::test_cleanup_respects_per_route_window
嚴重性: 🔴 嚴重
類型: 路由
[BUG-029] 配置監聽任務廣播半成品 TOML 並靜默吞掉異常
問題: 用戶手動編輯 config.toml 保存到一半(產生瞬時的語法錯誤)時,配置監聽後台線程會檢測到 mtime 變化、重載配置,但加載失敗後仍以空配置 {} 發射 config.updated 事件,導致適配器/模組的 on_config_update 收到空配置、誤以為所有配置項被清空而回退預設值。此外監聽循環用 except Exception: pass 靜默吞掉所有異常,watcher 故障無從排查。
原因: 兩個缺陷疊加:
_load_config在 TOML 語法錯誤/權限錯誤時把self._cache擦寫為{},但後台監聽線程_watch_loop與緩存超時路徑_check_cache_validity都在呼叫_load_config()後無條件執行_emit_config_updated(),把"加載失敗產生的空緩存"當作真實變更廣播。_watch_loop的except Exception改為以 warning 級別記錄(新增 i18n 鍵core.config.watcher_error,五語言同步)
根因鏈路:
用戶保存到一半 → TOML 語法錯誤
→ _load_config() 擦寫 _cache = {}
→ _watch_loop 無條件 _emit_config_updated(new_config={})
→ 適配器/模組 on_config_update 收到空配置
→ 誤判配置被清空,回退預設值
影響版本: 2.6.2-dev.1 - 2.7.0-dev.4
修復版本: 2.7.0-dev.5
修復內容:
_load_config改為返回bool;TOML 語法錯誤/權限/其他錯誤時保留上次有效緩存(不再擦寫為{}),僅記錄診斷日誌並返回False_watch_loop與_check_cache_validity僅在_load_config()返回True時才發射config.updated_watch_loop的except Exception改為以 warning 級別記錄(新增 i18n 鍵core.config.watcher_error,五語言同步)
修復日期: 2026/07/31
回歸測試: tests/unit/test_unit_config.py → test_malformed_toml_preserves_last_valid_cache、test_permission_denied_logs_clear_message(更新為驗證保留緩存 + 返回 False)
嚴重性: 🟡 中等
類型: 配置系統
[BUG-030] 配置 watcher 競態導致 setConfig 延遲寫入靜默丟數據
問題: 多個用戶報告使用 config.setConfig(key, value)(預設 immediate=False)後,自己的模組配置未寫入 config.toml,而其它模組的配置正常。設定 immediate=True(強制刷盤)可避開。表現為:運行期寫入的配置在下次重啟後丟失,啟動期模板生成的配置保留。
原因: 兩個疊加缺陷:
- 邏輯缺陷:
_watch_loop在_check_file_change()返回True時無條件_dirty_keys.clear()丟棄所有待寫鍵。但_check_file_change()僅用!=對比 mtime,框架自身的_flush_config寫盤也會改變 mtime——雖然_flush_config在寫盤後更新_config_mtime,但 watcher 線程在檔案寫入與 mtime 賦值之間(以及粗粒度檔案系統上)仍可能觀測到 mtime 差值,誤判為"外部修改"並清空全部待寫鍵。 - 線程缺陷:
_watch_loop操作_write_timer/_dirty_keys時未持有_lock,與setConfig(持鎖寫_dirty_keys)、_schedule_write(持鎖寫_write_timer)存在數據競爭。
根因鏈路:
模組A setConfig(immediate=True) → flush 寫盤,mtime 變化
→ 用戶模組 setConfig(immediate=False) → 進入 _dirty_keys,5s 後刷盤
→ watcher 輪詢,_check_file_change 觀測到先前自身寫入的 mtime 差值
→ _dirty_keys.clear() → 用戶模組的待寫鍵被靜默丟棄
→ 重啟後配置缺失
影響版本: 2.6.0 - 2.7.0
修復版本: 2.7.1
修復內容:
- 新增
_last_self_write_mtime字段,_flush_config寫盤後同步記錄;_check_file_change在 mtime 變化時先對比該值,匹配則判定為自身寫入返回False _watch_loop整段持_lock;真正外部修改時保留_dirty_keys(merge 語義),下次 flush 與外部內容合併(臟鍵優先),不再clear()getConfig/_check_cache_validity路徑不受影響(其 reload 本就不清臟鍵)
修復日期: 2026/08/06
回歸測試: tests/unit/test_unit_config.py → test_self_write_not_detected_as_external、test_external_change_preserves_dirty_keys、test_flush_merges_dirty_with_external
嚴重性: 🔴 嚴重
類型: 配置系統
[BUG-032] 配置延遲刷盤期間「寫後立讀」讀到舊值
問題: config.setConfig()(預設 immediate=False 延遲約 5 秒刷盤)寫入點分鍵後,立即讀取其父級/祖先節點(如 set_erispulse_section("scope.actions.MyModule", {...}) 後呼叫 get_erispulse_config())返回的是舊值,寫入的子鍵"消失",直到刷盤後才可見。作用域配置熱更新等"寫-讀-寫"場景受影響(2.8.0 測試插件 /t_section 用例暴露)。
原因: setConfig 將點分鍵以扁平形式存入待寫隊列 _dirty_keys,僅 getConfig 的精確鍵查詢命中待寫隊列;樹形路徑查詢(getConfig("ErisPulse.scope"))只走緩存樹,不疊加待寫值——延遲刷盤(_flush_config 才將臟鍵合併進緩存並清隊列)期間形成讀-你-寫斷層。
影響版本: 2.6.0 - 2.8.0-dev.1
修復版本: 2.8.0-dev.1
修復內容: getConfig 引入待寫疊加語義——① 精確命中待寫鍵直接返回(原有行為不變);② 待寫鍵是查詢鍵的祖先 → 取最長待寫祖先,在其值子樹內解析剩餘路徑;③ 待寫鍵是查詢鍵的後代 → 構建疊加子樹(_dirty_overlay)與緩存子樹深合併(_deep_merge,override 优先,不修改原緩存物件)。無待寫鍵時走原快路徑,零額外開銷。
修復日期: 2026/09/04
回歸測試: tests/unit/test_unit_config.py → test_get_config_overlays_dirty_descendant、test_get_config_overlay_merges_with_cache_siblings、test_get_config_overlay_new_branch、test_get_config_dirty_ancestor_query、test_get_config_dirty_exact_key_still_wins
嚴重性: 🟡 中等
類型: 配置系統
[BUG-033] wait_reply 掛起的回覆被高優先級處理器餓死
問題: 模組呼叫 wait_reply() 等待用戶回覆期間,若該回覆訊息被更高優先級的事件處理器認領(mark_processed()),命令分發器在入口看到 _processed 標記後直接返回,排在分發器末尾的回覆匹配邏輯永遠執行不到——等待方收不到回覆,只能乾等到超時返回 None。典型觸發場景:使用了高優先級訊息處理器(記錄/審計/攔截類)的機器人,所有對話式互動隨機性失效。
原因: 回覆匹配 _check_pending_reply 掛在 _handle_message 末尾(命令未匹配時才執行),而 _processed 檢查在其之前——認領檢查與回覆命中判定的順序顛倒。互動等待是框架級的會話延續機制而非競爭處理器,不應受其他處理器的認領影響。
影響版本: 2.2.0-dev.0 - 2.8.0-dev.1
修復版本: 2.8.0-dev.2
修復內容: 回覆命中判定提前至 _handle_message 入口(_processed 檢查之前、僅 message 事件):先嘗試完成掛起的對話,命中後事件被標記已處理、下方檢查自然短路;未命中則繼續原有命令匹配流程。同時判定鏈委託新的互動會話管理器(Core/Event/interaction.py),順帶獲得按歸屬取消與權限複查能力。
修復日期: 2026/09/08
回歸測試: tests/unit/test_unit_interaction.py(TestRegisterResolve 命中/未命中/認領標記)、tests/unit/test_unit_event.py(wait_reply 全鏈路)
嚴重性: 🟡 中等
類型: 事件系統 / 命令系統
[BUG-034] 作用域 persist=False 運行時綁定被任意後續配置寫入靜默沖掉
問題: scope.set_module(..., persist=False) 等運行時寫入僅修改記憶體 _data;但 scope 訂閱了 config.set / config.updated 事件,任意一處代碼寫配置(如某模組加載時寫自己的預設配置)都會觸發 scope 從配置檔案整體重建配置樹,此前所有運行時綁定靜默丟失(判定回退為預設放行),且無任何日誌提示。依賴運行時綁定的場景(Dashboard"僅運行時"開關、模組運行期動態禁用)在無關模組寫配置後行為回退。
原因: 根因鏈路:scope.set() 先把值寫入記憶體配置樹 _data(運行時綁定同樣直接寫入 _data)→ 持久化分支對整棵 _data 做深拷貝快照提交給 update_erispulse_config 差量落盤 → 快照中混入了 persist=False 綁定的值。delete(persist=True) 同源:把 _data 的活引用(parent 節點)交給延遲寫臟隊列,延遲刷盤期間對該節點下兄弟鍵的運行時修改會被一併落盤,且活引用在臟隊列滯留期間存在跨寫污染窗口。
影響版本: 2.8.0-dev.2 - 2.9.0-dev.0
修復版本: 2.9.0-dev.2
修復內容: 引入持久化基線 _persisted_tree(配置樹重建時以磁盤加載、校驗後的樹刷新)作為磁盤真相鏡像:set(persist=True) 只在基線上應用本次變更後提交差量,運行時綁定永不進入持久化內容;"寫後立讀"改用記憶體最終態快照直接恢復,不再經 _apply_tree 重建以免污染基線。delete(persist=True) 同口徑:在基線上應用刪除並把基線子樹的深拷貝交給持久化層;set_action 整體替換語義先按同口徑刪除再寫入,舊規則鍵不殘留在持久化內容中。
修復日期: 2026/09/27
複現步驟: ① 模組內執行 scope.set("bots.p.debug_mode", {"blocked": ["X"]}, persist=False);② 觸發任意持久化寫入(如另一模組呼叫 set_module);③ 打開 config/config.toml,可見 debug_mode 已被寫入(修復前);④ 卸載該模組(運行時綁定被註銷)後觸發配置重載,scope.get("bots.p.debug_mode") 仍返回綁定值。
回歸測試: tests/unit/test_unit_scope.py::TestPersistBaseline(運行時綁定不隨無關持久化寫入落盤 / 卸載註銷後配置重載不復活 / delete 提交基線子樹不帶運行時兄弟鍵 / set_action 替換不留殘留鍵 / cache_size 配置生效)
嚴重性: 🔴 嚴重
類型: 配置系統
[BUG-035] 配置面板 select 選項與字典字段渲染為 [object Object]
問題: WebUI 配置面板中,select 字段的選項下拉顯示 [object Object](如動態生成的配色風格選項);未聲明控件類型的 dict 字段(如 stalker_mode、knowledge_base 等嵌套配置段)在文本框中顯示 [object Object],無法正常查看與編輯。
原因: 兩處獨立缺陷:① 框架 i18n 解析器 _resolve_i18n_text 僅還原帶 i18n 鍵的字典,選項標籤為僅含 default 的字典(無 i18n 鍵的動態文本)時原樣透傳,前端 esc(label) 字串強轉得到 [object Object];② Dashboard 渲染分支只對 array 類型做 JSON textarea,dict 值落入純文本輸入分支被 String() 強轉。此外模組若將 _schema_meta 誤聲明為普通 dataclass 字段(缺 ClassVar 注解),會作為配置字段進入 schema 加劇混亂。
影響版本: 2.7.0 - 2.8.0-dev.2
修復版本: 2.8.0-dev.2
修復內容: ① _resolve_i18n_text 支持僅含 default 的字典還原為文本;② 框架 schema/模板/預設值/填充/校驗五處一律排除下劃線前綴字段(誤聲明無害化);③ Dashboard select 選項 label 對象兜底解析(default 优先)、dict/table 字段渲染為 JSON textarea(保存路徑按 tp=object JSON.parse 回寫,完整往返)。
修復日期: 2026/09/09
回歸測試: tests/unit/test_unit_config.py::TestResolveI18nDefaultOnlyDict、TestSchemaUnderscoreFieldExclusion
嚴重性: 🟡 中等
類型: 配置系統
[BUG-036] 多實例共享配置目錄時配置寫入偶發失敗(ENOENT)
問題: Docker 部署場景下(多容器掛載同一宿主機配置目錄),日誌偶發連續兩條 Failed to write configuration file ... [Errno 2] No such file or directory: '...config.toml.tmp' -> '...config.toml'。該次配置寫入被丟棄(舊配置完整保留,未觀察到配置丟失),功能不受影響,但告警反覆出現干擾排查,且待寫入的配置項需等待下次寫入才能落盤。
原因: 根因鏈路:_flush_config / setConfigTemplate 使用固定名暫存檔 config.toml.tmp 承載新內容,write() 後不 fsync 直接 rename()。兩個 ErisPulse 實例共享同一配置目錄時,B 實例 open("w") 可能 truncate A 實例正在寫的暫存檔 → A rename 時目標已被 B 取走或內容被截斷,報 ENOENT(即用戶日誌中的連續兩條錯誤)。已報告案例中僅表現為寫入失敗告警(舊配置保留);若交錯時序更極端,rename 可能輸出空/半截的 config.toml(屬潛在風險,尚未在真實環境爆發)。單實例場景下 ext4 延遲分配同樣存在「rename 元數據先於數據塊落盤」的崩潰窗口(SIGKILL / 斷電)。_file_lock 為進程內 threading.RLock,對跨進程/跨容器寫入無約束。
影響版本: 2.2.0-dev.0 - 2.8.0
修復版本: 2.8.1
修復內容: 配置寫入統一收斂到 _atomic_write_text():同目錄 mkstemp 生成進程唯一暫存檔(消除固定名爭搶,多實例下退化為 last-writer-wins,不再 ENOENT)→ 寫畢 flush + fsync 強制數據落盤(消除「rename 已生效、數據未落盤」窗口)→ os.replace 原子替換目標(POSIX/Windows 均原子,任一時刻磁碟上要么完整舊內容、要么完整新內容);POSIX 下額外 fsync 配置目錄。_flush_config、setConfigTemplate、根目錄配置遷移三處寫入點全部切換;異常路徑清理邏輯隨唯一暫存檔名重構。新增多實例檢測:啟動時 advisory lock(POSIX flock / Windows msvcrt.locking)獨占持有配置目錄鎖檔 .erispulse_config.lock,被占用即輸出 i18n 告警(不阻塞啟動),鎖由 OS 在進程退出時自動釋放、無幽靈鎖。
修復日期: 2026/09/13
複現步驟: ① 兩個容器掛載同一宿主機 config/ 目錄並同時運行 ErisPulse;② 任一實例觸發配置寫入(如模組註冊預設配置);③ 觀察日誌出現 ENOENT 寫失敗告警,本次寫入被丟棄(舊配置保留)。
關聯: 用戶報告(1Panel 容器 ×2)
回歸測試: tests/unit/test_unit_config_atomic_write.py(內容完整寫入/無暫存檔殘留/寫失敗保原文件/雙實例併發寫入檔案始終合法/鎖檔創建/多實例告警/遷移原子寫入)
嚴重性: 🟢 輕微
類型: 配置系統
[BUG-037] 空格子命令名註冊後永遠無法被觸發
問題: 以空格分隔的多 token 命令名註冊子命令(如 @command("admin add"))後,命令可正常註冊並出現在幫助列表中,但用戶發送 /admin add 時機器人永遠無回應——輸入被父 token 命令匹配為 admin + 參數 ["add"];若父 token 也未註冊則完全無回應。僅當使用點分命名(admin.reload,整體為單 token)時可避開。
原因: 根因鏈路:CommandHandler.__call__ 將任意命令名(含空格形式)原樣存入平鋪的 self.commands 字典 → 分發階段 _try_execute_command 僅取消息首 token 匹配(cmd_name = parts[0])→ 多 token 命令名作為字典鍵永遠查不到。註冊與匹配兩階段對命令名的空間假設不一致,且無任何註冊期告警(靜默失效)。
影響版本: 引入命令系統起 - 2.8.0
修復版本: 2.8.1
修復內容: 匹配層改為最長前綴匹配:從最長候選(" ".join(parts[:n]),n 上限為已註冊命令名/別名的最大 token 數緩存 _max_name_tokens)逐級降級嘗試,命中即以剩餘 token 為參數執行,下游作用域/ACL/覆寫/master/權限鏈對命令全名自然生效。配套語義:父子並存時未註冊的子命令輸入回落父命令(歷史行為不變);子命令未聲明 permission 時沿父鏈繼承最近聲明權限的祖先命令(保護父命令即保護其下全部子命令);僅註冊單 token 命令時首輪即命中,分發開銷與原先一致。unregister / unregister_by_owner / 全量清理同步維護 token 數緩存。
修復日期: 2026/09/13
複現步驟: ① 模組內 @command("admin add") 註冊;② 發送 /admin add x;③ 修復前無回應(或被同時註冊的 /admin 以參數形式接住),修復後 admin add 觸發且 get_command_args() 為 ["x"]。
回歸測試: tests/unit/test_unit_command_subcommand.py(最長前綴匹配/僅子命令觸發/三級嵌套/大小寫敏感兩模式/單與多 token 別名/事件載體全名/生命週期鉤子全名/權限繼承六例/ACL glob 全名/master/註銷回落與緩存重算)
嚴重性: 🟡 中等
類型: 事件系統
[BUG-038] persist=False 運行時綁定被持久化寫入順帶落盤,模組卸載後從磁碟"復活"
問題: 以 scope.set(path, value, persist=False) 寫入的運行時綁定(文件承諾不落盤、進程重啟即失效、模組卸載時清理)會隨任意一次無關的 persist=True 寫入(預設值,如模組 set_module / WebUI 保存配置)被差量寫入用戶 config.toml;此後即使模組卸載註銷了運行時綁定,下次配置重載時該綁定仍從磁碟"復活"繼續生效、進程重啟後依然存在——與"運行時綁定不落盤"的語義契約相反,且極難排查。
原因: 根因鏈路:ScopeManager.set() 先把值寫入記憶體配置樹 _data(運行時綁定同樣直接寫入 _data)→ 持久化分支對整棵 _data 做深拷貝快照提交給 update_erispulse_config 差量落盤 → 快照中混入了 persist=False 綁定的值。delete(persist=True) 同源:把 _data 的活引用(parent 節點)交給延遲寫臟隊列,延遲刷盤期間對該節點下兄弟鍵的運行時修改會被一併落盤,且活引用在臟隊列滯留期間存在跨寫污染窗口。
影響版本: 2.8.0-dev.2 - 2.9.0-dev.0
修復版本: 2.9.0-dev.2
修復內容: 引入持久化基線 _persisted_tree(配置樹重建時以磁盤加載、校驗後的樹刷新)作為磁盤真相鏡像:set(persist=True) 只在基線上應用本次變更後提交差量,運行時綁定永不進入持久化內容;"寫後立讀"改用記憶體最終態快照直接恢復,不再經 _apply_tree 重建以免污染基線。delete(persist=True) 同口徑:在基線上應用刪除並把基線子樹的深拷貝交給持久化層;set_action 整體替換語義先按同口徑刪除再寫入,舊規則鍵不殘留在持久化內容中。
修復日期: 2026/09/27
複現步驟: ① 模組內執行 scope.set("bots.p.debug_mode", {"blocked": ["X"]}, persist=False");② 觸發任意持久化寫入(如另一模組呼叫 set_module);③ 打開 config/config.toml,可見 debug_mode 已被寫入(修復前);④ 卸載該模組(運行時綁定被註銷)後觸發配置重載,scope.get("bots.p.debug_mode") 仍返回綁定值。
回歸測試: tests/unit/test_unit_scope.py::TestPersistBaseline(運行時綁定不隨無關持久化寫入落盤 / 卸載註銷後配置重載不復活 / delete 提交基線子樹不帶運行時兄弟鍵 / set_action 替換不留殘留鍵 / cache_size 配置生效)
嚴重性: 🔴 嚴重
類型: 配置系統
[BUG-039] 整節寫與點分寫並存時讀寫不一致(點分覆寫丟失)
問題: 延遲刷盤窗口內,同節並存整節寫(setConfig("Mod", {...}),如 BaseModule.cfg 寫回)與點分寫(setConfig("Mod.key", v),如配置熱更 / 測試工具覆寫)時有兩個變體:變體 A(讀路徑)——getConfig("Mod") 整節讀取返回舊整節待寫快照,看不見更晚的點分覆寫(點分讀路徑正常);變體 B(寫路徑,更重)——點分寫在前、整節寫在後(測試工具注入覆寫 → 模組 self.cfg = ... 寫回的常見時序)時,flush 按插入序應用臟鍵,整節寫整體替換該節,點分覆寫在磁碟上永久丟失。生產環境"配置熱更 + 模組運行時寫回"組合即可觸發,與測試環境無關(ErisPulse-DailyCard 測試反饋暴露)。
原因: getConfig 第①段(精確命中 _dirty_keys)提前 return self._dirty_keys[key],完全繞過第④段 _dirty_overlay 後代覆寫;_flush_config 按 _dirty_keys 插入序應用臟鍵,整節寫恰好排在點分寫之後時點分值被整體替換。另:第①段返回臟隊列原物件引用,呼叫方原地修改返回 dict 會直接改動待落盤狀態。
影響版本: 2.6.0 - 2.9.0-dev.2
修復版本: 2.9.0-dev.2
修復內容: 脏窗口內統一為特異性優先語義——點分(更具體)待寫值優於整節(較寬)待寫值,讀路徑與落盤路徑同口徑:① getConfig 精確命中待寫鍵後仍疊加 _dirty_overlay 後代待寫值(非 dict 值返回疊加子樹,與③+④標量邊角同口徑);② _flush_config 脏鍵按路徑深度排序應用(整節/祖先先、點分/後代後,穩定排序保持同深度寫入時序)。代價:同一臟窗口內(約 5 秒)整節寫回無法覆蓋仍掛起的點分寫——讀路徑修復後讀-改-寫自然攜帶點分值,實際影響面極小。③ 涉及待寫值的 getConfig 返回(精確命中 / 祖先子樹 / 疊加合併)改為 copy.deepcopy 隔離拷貝,不再泄漏臟隊列內部引用;無臟鍵快路徑與純緩存讀行為不變。
修復日期: 2026/09/28
複現步驟: ① setConfig("FB.y", 1);② setConfig("FB", {"z": 2});③ force_save()——修復前磁碟只剩 [FB] z = 2(y 丟失),修復後 {"y": 1, "z": 2};變體 A:步驟①②後不落盤直接 getConfig("FB")——修復前不含 y,修復後可見。
回歸測試: tests/unit/test_unit_config.py::TestSectionAndDottedDirtyConsistency(變體 A 兩種插入序 / 變體 B 落盤與順序無關 / 三層混合存活 / 隔離拷貝 / 祖先子樹隔離)
嚴重性: 🟡 中等
類型: 配置系統
[BUG-040] 平台切段訊息的命令尾部參數靜默丟失
問題: 平台把一條訊息切成多個 text 段(如 /kick 與參數之間夾著 mention 段、或輸入法分段)時,命令分發只取第一個 text 段做匹配——/艾特 @xxx 123 解析為無參數的 艾特,聲明了 args= 的命令直接報"參數缺失",且無任何留痕。
原因: command.py 的文本提取迴圈取到首個 type=="text" 段後 break,其餘段(含全部非文本段)被丟棄。
影響版本: 2.6.0(命令分發引入)- 2.10.0
修復版本: 2.10.1-dev.0
修復內容: 命令文本改為按段順序收集全部 text 段空格拼接;mention/圖片夾層不再導致命令整體無法解析;同步補齊畸形段容錯(message 為 None/字串/非 dict 段不再擊穿分發器)。
回歸測試: tests/unit/test_unit_command_multiseg.py
嚴重性: 🟡 中等 類型: 事件系統
[BUG-041] 被拒絕命令經 alt_message 整鏈重跑:重複回覆與權限函數副作用雙觸發
問題: 適配器的 alt_message 與訊息段文本不一致時(如 alt 含 @文本前綴),命中命令被 scope/ACL/master/permission 拒絕後,分發器用 alt_message 把整條鏈重跑一遍——用戶收到兩條"權限不足"回覆,自定義權限函數(常帶查庫/計數副作用)被調用兩次。
原因: 拒絕路徑 return False 與"未命中命令"共用同一回傳值,分發器據此認為可嘗試 alt 兜底;而事件在拒絕前已被認領,重跑僅多執行了回覆與權限判定。
影響版本: 2.9.0(治理/權限鏈成形)- 2.10.0
修復版本: 2.10.1-dev.0
修復內容: 文本處理改三態契約(unmatched/consumed/rejected)——命中註冊命令後(無論拒絕與否)不再 alt 回退;僅未命中任何命令時保持 alt 兜底。
回歸測試: tests/unit/test_unit_command_multiseg.py::test_denied_command_not_rerun_via_alt
嚴重性: 🟡 中等 類型: 事件系統
[BUG-042] 命令處理器內註冊的 wait_reply 被同一條觸發訊息立即消費
問題: 命令執行體內呼叫 wait_reply / 註冊交互等待時,等待被同一條命令訊息立即命中——交互流首問被跳過,"用戶回覆"實為命令文本本身(答非所問/數據污染)。
原因: _handle_message 在方法開頭與結尾各呼叫一次 interaction.resolve,命令執行後結尾的二次 resolve 會命中執行期間新註冊的等待;cmdpass 關閉時 alt 缺失場景判定為空字串直接放行,加重了該問題。
影響版本: 2.8.0(交互會話系統引入)- 2.10.0
修復版本: 2.10.1-dev.0
修復內容: 分發鏈收斂為單次 interaction.resolve(方法開頭處),刪除結尾二次呼叫;等待統一由下一條訊息消費。
回歸測試: tests/unit/test_unit_command_multiseg.py::test_wait_reply_not_consumed_by_command_trigger
嚴重性: 🟡 中等 類型: 事件系統
[BUG-043] select() 同鍵多路等待後者靜默取消前者
問題: event.select() 的多條期望推導出同一會話鍵(同 user 不同 pattern、多條 session 期望等)時,後註冊路把先註冊路以"衝突"取消,select 直接拋 InteractionCancelled 而非文件承諾的超時返回 (None, None)。
原因: 每路獨立呼叫 interaction.register,其互斥語義對同鍵舊等待發 REASON_CONFLICT 取消;註釋聲稱"同 user 多路因事件副本獨立互不衝突"不成立——副本只改 user_id,同 user 多路的鍵完全相同。
影響版本: 2.9.0(select 引入)- 2.10.0
修復版本: 2.10.1-dev.0
修復內容: select 改為聚合等待——全部期望合併為自定義 matcher 的單條目(按鍵集合登記、共享同一 future),任一路命中即返回對應下標;同鍵多路天然共存。
回歸測試: tests/unit/test_unit_command_multiseg.py::test_select_same_user_multi_branch
嚴重性: 🟡 中等 類型: 事件系統
[BUG-044] BaseConverter.at() 產出 OB11 遺留 "at" 段,全框架 mention 判定不識別
問題: 適配器用 BaseConverter.at() 組裝入站事件時,must_at_bot / has_mention() / on_at_message 全部失明——@機器人命令在群里靜默失效。
原因: at() 產出 {"type": "at"}(OB11 形態),而框架全部 mention 判定與構建側(MessageBuilder/發送修飾器)均為 OB12 的 "mention",雙軌並存且無任何告警。
影響版本: 引入版本不早於 2.5.0 - 2.10.0
修復版本: 2.10.1-dev.0
修復內容: at() 改產 {"type": "mention", "data": {"user_id": ...}}(方法名保留別名,新增 mention 別名)。
回歸測試: tests/unit/test_unit_command_multiseg.py::test_converter_at_produces_mention
嚴重性: 🟡 中等 類型: 適配器
[BUG-045] 無 alt_message 的適配器上交互原語永久超時、命令穿透失效
問題: 適配器只填 message 段不填 alt_message 時:confirm / choose / collect 對用戶明文回覆(如"是")永久等待;等待期間的已註冊命令永遠無法穿透執行;Event.get_text() 返回空字串導致腳手架示例靜默失效。
原因: 上述路徑全部直取 alt_message 字串、無訊息段回退——框架"alt_message 為適配器可選字段"的契約下,僅段填充是合法形態。
影響版本: 各交互原語引入版本 - 2.10.0
修復版本: 2.10.1-dev.0
修復內容: 統一走 alt_message 优先 + text 段回退的 extract_text 口徑(交互原語五處取值/校驗點、cmdpass 判定、Event.get_text()/get_message_text())。
回歸測試: tests/unit/test_unit_command_multiseg.py
嚴重性: 🟡 中等 類型: 事件系統
[BUG-046] 聲明式配置模板的嵌套子表頭漂移為頂層表
問題: 嵌套 dataclass 聲明的配置(ConfigClass 內嵌 dataclass 字段)首次生成配置模板時,子表頭 [Nested] 落在檔案頂層而非組件節內——適配器讀 cfg.嵌套字段 拿不到值;config.full.example 中同樣錯位。
原因: 模板生成器對嵌套 dataclass 渲染相對表頭([Nested]),而 TOML 表頭語義是從根算起的絕對路徑;setConfigTemplate 舊實現把解析後的表物件搬進組件節、full.example 直接文本拼裝,兩條路徑都未把相對表頭重錨到組件路徑下。
影響版本: 2.10.0 - 2.10.1(嵌套 dataclass 模板渲染隨該版本引入)
修復版本: 2.10.2
修復內容: setConfigTemplate 按模板解析出的子表路徑集合精確重錨表頭([Nested] → [組件名.Nested]);config.full.example 生成側改傳 _prefix 直接產出絕對表頭。
回歸測試: tests/unit/test_unit_config.py::TestTomlclassEngineMigration::test_template_write_idempotent_and_nested_anchored
嚴重性: 🟡 中等 類型: 配置系統 / 適配器
[BUG-047] 服務目錄全量枚舉漏掉懶加載模組
問題: sdk.module.services() 全量目錄對未實例化(懶加載)模組一律跳過——模組聲明了 meta.services 也不出現在目錄中,以目錄為發現入口的消費方(AI 智能體、help 模塊等)無法發現其服務;單模組精確查詢 services("Name") 與拓撲 get_topology() 不受影響。
原因: ModuleManager.services() 全量枚舉分支對無契約緩存(未加載)的模組在現場解析契約之後仍無條件 continue——本意是跳過"未聲明服務的模組",實際把未加載模組整體跳過了;已加載模組因加載時寫入契約緩存不受影響。
影響版本: 2.9.0 - 2.10.1(模組 RPC 與服務目錄隨 2.9.0 引入)
修復版本: 2.10.2
修復內容: 刪除無條件跳過,統一由"解析結果為空才跳過"裁決;未加載模組現場解析 get_meta().services(與 module.call() 白名單同口徑),聲明了服務的照常進入目錄。
回歸測試: tests/unit/test_unit_module_rpc.py::TestServicesDirectory::test_lazy_declared_module_in_full_directory
嚴重性: 🟡 中等 類型: 加載系統
[BUG-048] 會話收件箱查詢 API 丟失訊息歸因(sender 與事件元數據)
問題: 收件箱所有查詢接口(get() / get_by_trace() / event.history())返回的記錄只有 role/text/ts/event_id——儲存層已存在的 sender 列被查詢層丟棄,群聊回放時不同成員的訊息在 API 層不可區分(AI 把成員 B 誤認為上一輪對話者 A);入站記錄同時不歸檔事件時刻的昵稱等元數據,事後反查拿到的是"現在的昵稱"而非"當時的昵稱"。
原因: 2.8.0 加 sender 列時的註釋即寫明用於"回放時還原訊息來源",但 Select 與返回構造均未攜帶(實現疏漏);元數據歸檔則從未設計——append() 無 meta 通道,表結構無對應列。
影響版本: 2.8.0 - 2.11.0(sender 列隨 2.8.0 引入)
修復版本: 2.11.1-dev.0
修復內容: 新增 meta 列(白名單契約:入站 nickname/self_id/sub_type/message_id,出站發送上下文與 module;舊表自動遷移,後端不支援 ALTER 時靜默回退 {});append() 增加 meta 參數貫穿記憶體緩衝與批量落盤;get / recent / get_by_trace / event.history 統一返回 sender + meta(JSON 損壞回退 {})。
關聯: #467(同批 #468 / #469 / #470)
回歸測試: tests/unit/test_unit_transcript_attribution.py::TestSenderMetaReadback / TestMetaMigration
嚴重性: 🟡 中等 類型: 會話收件箱 / 存儲系統
[BUG-049] 入站 bot 自發訊息回聲被記為 user,污染收件箱歸因
問題: 開啟訊息回顯的平台/協議端(OneBot v11 report-self-message、Telegram outgoing 等)把 bot 自己發的訊息以 message 事件上報時,收件箱一律記 role="user"——機器人自發言與真實使用者輸入不可區分,下游模組把 bot 自發言當成使用者輸入處理(AI 據此回應"使用者"說過的話),role 統計失真。
原因: 入站自動記錄點(Core/adapter.py)對所有 event_type == "message" 無條件記 role="user",未消費 self 字段做 bot 回聲判定——而緊隨其後的 bot 自動發現邏輯已在消費同一字段(記錄時刻可用,記錄邏輯漏用)。
影響版本: 2.8.0 - 2.11.0(收件箱自動記錄隨 2.8.0 引入)
修復版本: 2.11.1-dev.0
修復內容: 記錄前按 self.user_id == user_id 识别回聲,按新配置 ErisPulse.transcript.self_echo 三模式處理:"bot"(預設)記 role="bot" + sender=self_id + meta.self_echo=true;"skip" 不記錄;"user" 保持旧行為。非回聲平台(self 缺失或不相等)逐字段回歸不變。
關聯: #470(依賴 #467 的 meta 列承載 self_echo 標記)
回歸測試: tests/unit/test_unit_transcript_attribution.py::TestSelfEcho
嚴重性: 🟡 中等 類型: 適配器 / 會話收件箱
[BUG-050] 熱重啟後模組加載失敗導致進程崩潰
問題: 熱重啟後,如果某個模組加載失敗(如依賴缺失、配置錯誤),整個進程會崩潰,而不是只記錄錯誤日誌。
原因: 當前的模組加載系統在熱重啟過程中,如果某個模組加載失敗,會直接拋出異常,導致整個進程退出。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載系統,使其在熱重啟過程中,如果某個模組加載失敗,只記錄錯誤日誌,而不會導致整個進程退出。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_hot_restart_module_load_failure
嚴重性: 🔴 嚴重
類型: 加載系統
[BUG-051] 模組加載器未處理模組依賴循環
問題: 當模組之間存在依賴循環時,模組加載器無法正確處理,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組依賴時,未檢測和處理依賴循環。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠檢測和處理模組依賴循環,並在發現循環時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_dependency_cycle
嚴重性: 🟡 中等
類型: 加載系統
[BUG-052] 模組加載器未處理模組版本不匹配
問題: 當模組版本與框架版本不匹配時,模組加載器無法正確處理,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組版本時,未檢測和處理版本不匹配的情況。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠檢測和處理模組版本不匹配的情況,並在發現不匹配時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_version_mismatch
嚴重性: 🟡 中等
類型: 加載系統
[BUG-053] 模組加載器未處理模組配置錯誤
問題: 當模組配置錯誤時,模組加載器無法正確處理,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組配置時,未檢測和處理配置錯誤的情況。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠檢測和處理模組配置錯誤的情況,並在發現錯誤時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_config_error
嚴重性: 🟡 中等
類型: 加載系統
[BUG-054] 模組加載器未處理模組文件損壞
問題: 當模組文件損壞時,模組加載器無法正確處理,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組文件時,未檢測和處理文件損壞的情況。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠檢測和處理模組文件損壞的情況,並在發現損壞時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_file_corrupted
嚴重性: 🟡 中等
類型: 加載系統
[BUG-055] 模組加載器未處理模組加載超時
問題: 當模組加載超時時,模組加載器無法正確處理,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組加載時,未檢測和處理加載超時的情況。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠檢測和處理模組加載超時的情況,並在發現超時時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_load_timeout
嚴重性: 🟡 中等
類型: 加載系統
[BUG-056] 模組加載器未處理模組加載失敗的重試機制
問題: 當模組加載失敗時,模組加載器無法正確處理重試機制,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組加載失敗時,未實現重試機制。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠實現模組加載失敗的重試機制,並在重試失敗時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_load_retry
嚴重性: 🟡 中等
類型: 加載系統
[BUG-057] 模組加載器未處理模組加載的並行執行
問題: 當模組加載需要並行執行時,模組加載器無法正確處理,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組加載時,未實現並行執行機制。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠實現模組加載的並行執行機制,並在並行執行失敗時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_load_parallel
嚴重性: 🟡 中等
類型: 加載系統
[BUG-058] 模組加載器未處理模組加載的異步執行
問題: 當模組加載需要異步執行時,模組加載器無法正確處理,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組加載時,未實現異步執行機制。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠實現模組加載的異步執行機制,並在異步執行失敗時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_load_async
嚴重性: 🟡 中等
類型: 加載系統
[BUG-059] 模組加載器未處理模組加載的同步執行
問題: 當模組加載需要同步執行時,模組加載器無法正確處理,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組加載時,未實現同步執行機制。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠實現模組加載的同步執行機制,並在同步執行失敗時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_load_sync
嚴重性: 🟡 中等
類型: 加載系統
[BUG-060] 模組加載器未處理模組加載的錯誤回退機制
問題: 當模組加載發生錯誤時,模組加載器無法正確處理回退機制,導致某些模組加載失敗或行為異常。
原因: 模組加載器在處理模組加載時,未實現錯誤回退機制。
影響版本: 2.10.0 - 2.11.0
修復版本: 2.11.1-dev.0
修復內容: 修改模組加載器,使其能夠實現模組加載的錯誤回退機制,並在回退失敗時記錄錯誤日誌。
修復日期: 2026/09/30
回歸測試: tests/unit/test_unit_module_load.py::test_module_load_rollback
嚴重性: 🟡 中等
類型: 加載系統