简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

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 按需读取:只读需要的章节即可,不必全文加载。

异常捕获总览

来源: 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 >!--}

  1. 想寬泛兜底:捕獲 ErisPulseError(所有框架異常的基類)
  2. 想精確處理:按模組捕獲(如 ModuleCallTimeoutError、StorageUnreachableError)
  3. 儲存操作預設不拋出:失敗時記錄日誌並返回 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:
    ...  # 統一兜底:所有框架自定義異常

關閉噪音折疊

長期駐留進程在關閉(或背景循環被取消)時,事件循環常會刷出一批無處理價值的 Task was destroyed but it is pending! 類解釋器噪音。框架內建同類噪音折疊: 2 秒視窗內的同類訊息降級為 TRACE 並合併計數,只輸出一次,結尾帶 「(另有 N 次同類訊息被折疊)」後綴。這是刻意的降噪行為——看到折疊後綴不代表 框架吞掉了真實異常,業務異常(上表各類)照常完整輸出。折疊僅針對解釋器退出噪音, 業務異常不受影響。什麼級別的異常值得留痕、什麼可以降級/折疊,遵循 工程健壯性規則 · 異常留痕分級。

相關文件

根據排查意圖選擇:

已知问题追踪

来源: 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

修復內容:

  1. 添加檔案鎖機制(_file_lock)確保檔案操作原子性
  2. 使用暫存檔案寫入後原子性重命名(os.replace/os.rename)
  3. 改進 _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

修復內容:

  1. 將 ASGI 伺服器從 Hypercorn 切換為 Uvicorn(pyproject.toml 依賴變更)
  2. 使用 uvicorn.Server._serve() 直接啟動伺服器,繞過 capture_signals() 信號處理上下文管理器
  3. 透過 server.should_exit = True 實現優雅停止,超時則取消背景任務
  4. 同步移除子進程運行模型和 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 客戶端在併發場景下存在多個穩定性缺陷,會導致連接泄漏或進程崩潰:

原因: 客戶端初次實現(2.4.6-dev.5)缺少併發保護與異常分類,對 aiohttp 異常體系與 ErisPulse 自定義異常的繼承關係處理不當。

影響版本: 2.4.6-dev.5 - 2.4.8

修復版本: 2.4.8

修復內容:

  1. 新增 _recv_lock 序列化所有 receive() / receive_text() / receive_bytes() 調用
  2. 新增 _session_lock 保護 session 創建;_drain_sessions() 改為異步方法並真正關閉舊 session
  3. 重構 request() 異常捕獲順序:asyncio.TimeoutError → aiohttp.ClientConnectionError(觸發 session 重建)→ aiohttp.ClientError → ClientError(透傳)→ Exception
  4. 修復 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

修復內容:

  1. 路由註冊時透過 current_owner ContextVar 自動追蹤 owner → namespace 歸屬關係
  2. 新增 unregister_all_by_owner(owner),停止/重啟時同時按 owner 清理,覆蓋細顆粒度命名空間
  3. 新增 _stop_adapter(platform) 原語("停止即清理"),將停止適配器與回收其註冊的資源綁定在一次呼叫裡,restart() 和啟動失敗重試均經此入口
  4. 新增框架級 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("*") 被 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 的列校驗從白名單模式改為黑名單模式:

  1. 新增 _validate_select_column() 函數,僅攔截 SQL 注入危險字元(; ' " -- /* */ \x00 換行符)
  2. 允許任意合法 SQL 列表達式(*、table.*、table.column、COUNT(*)、col AS alias 等)
  3. 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() 邏輯保持不變,完全向後兼容:

修復日期: 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

修復內容:

  1. 預創建中間層時始終使用字典,不再根據下一段是否為數字猜測容器類型
  2. 設定最終值時,僅當容器本身已是列表且索引小於 STORAGE_MAX_LIST_INDEX(10000)時才按索引處理,超大索引安全跳過
  3. 將遞迴實現改為迭代實現,消除原代碼中潛在的無限遞迴風險
  4. 新增 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 個回歸用例

嚴重性: 🔴 嚴重

類型: 存儲


[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 故障無從排查。

原因: 兩個缺陷疊加:

  1. _load_config 在 TOML 語法錯誤/權限錯誤時把 self._cache 擦寫為 {},但後台監聽線程 _watch_loop 與緩存超時路徑 _check_cache_validity 都在呼叫 _load_config() 後無條件執行 _emit_config_updated(),把"加載失敗產生的空緩存"當作真實變更廣播。
  2. _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

修復內容:

  1. _load_config 改為返回 bool;TOML 語法錯誤/權限/其他錯誤時保留上次有效緩存(不再擦寫為 {}),僅記錄診斷日誌並返回 False
  2. _watch_loop 與 _check_cache_validity 僅在 _load_config() 返回 True 時才發射 config.updated
  3. _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(強制刷盤)可避開。表現為:運行期寫入的配置在下次重啟後丟失,啟動期模板生成的配置保留。

原因: 兩個疊加缺陷:

  1. 邏輯缺陷:_watch_loop 在 _check_file_change() 返回 True 時無條件 _dirty_keys.clear() 丟棄所有待寫鍵。但 _check_file_change() 僅用 != 對比 mtime,框架自身的 _flush_config 寫盤也會改變 mtime——雖然 _flush_config 在寫盤後更新 _config_mtime,但 watcher 線程在檔案寫入與 mtime 賦值之間(以及粗粒度檔案系統上)仍可能觀測到 mtime 差值,誤判為"外部修改"並清空全部待寫鍵。
  2. 線程缺陷:_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

修復內容:

  1. 新增 _last_self_write_mtime 字段,_flush_config 寫盤後同步記錄;_check_file_change 在 mtime 變化時先對比該值,匹配則判定為自身寫入返回 False
  2. _watch_loop 整段持 _lock;真正外部修改時保留 _dirty_keys(merge 語義),下次 flush 與外部內容合併(臟鍵優先),不再 clear()
  3. 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

嚴重性: 🟡 中等

類型: 加載系統