🐛 Whale 程序 Bug 分析报告
经过对 25+ 核心文件的深入审查,发现了以下 严重、中等、轻微 三个级别的 Bug:
────────────────────────────
🔴 严重级别(可能导致崩溃、数据损坏或安全漏洞)
/open 命令存在路径穿越漏洞(Path Traversal)
文件: internal/app/open_command.go:124-132 — 函数 ResolveOpenPath
问题: /open ../../etc/passwd 可以打开工作区以外的任意系统文件。与所有其他文件工具(read_file、edit、write 等)不同,该函数没有任何工作区边界检查 —
没有调用 safePath/safeWorkspacePath。
abs, err := filepath.Abs(filepath.Clean(target))
// ❌ 缺少: if !strings.HasPrefix(abs, workspaceRoot) { return error }
────────────────────────────
2. 后台 shell goroutine panic 导致任务泄漏
文件: internal/tools/shell.go:49-54 → 调用 internal/tools/tasks.go:83-90
问题: 若 runShellBackground() 中 panic(如 shell.Resolve 遇到 nil 指针),b.tasks.completed(task.ID) 永远不会被调用。任务永远卡在 "running"
状态,清理逻辑只删除非 running 状态的任务 — 每 panic 一次就泄漏一个注册表条目。
────────────────────────────
3. turn_loop.go 中 channel send 无 context 保护,存在死锁风险
文件: internal/agent/turn_loop.go:78,87,100,117,119,143,145,155,161,163,170
问题: out <- AgentEvent{...} 在超过 15 个地方被执行,均没有用 select { case <-ctx.Done(): ...; case out <- ev: } 保护。如果消费者停止读取
channel(context
取消或不再 drain),goroutine 永久阻塞 — goroutine 泄漏 + 死锁。
────────────────────────────
4. os.Chdir 非 goroutine-safe 导致工作目录混乱
文件: internal/app/worktree_exit.go:76-78,96
问题: os.Chdir() 修改的是进程级工作目录。在并发场景下(或 hook 回调中),其他 goroutine 使用相对路径会静默地使用错误目录。且第 96 行忽略
os.Chdir(previousCwd) 错误:
_ = os.Chdir(previousCwd) // 如果失败,进程永久困在错误目录
────────────────────────────
5. reloadPluginDisabledConfig 静默忽略错误
文件: internal/app/commands.go:636-639 vs commands.go:624-627
问题: reloadPluginDisabledConfig 调用了 ApplyLoadedConfig(&cfg, loaded) 但丢弃了错误返回值。如果项目配置无效,PluginsDisabled
会被静默地用错误数据更新。对比 reloadSkillDisabledConfig(第 624 行)正确处理了错误。
────────────────────────────
6. PatchSessionMeta 无法将 TotalCostUSD/TurnCount 清零
文件: internal/session/meta_state.go:130-140
问题: 条件 if patch.TotalCostUSD != 0 { cur.TotalCostUSD = patch.TotalCostUSD } 用 0
作为"不修改"的哨兵值,导致一旦累积了费用或轮次,就无法重置回零。恢复旧 session 时成本数据会持续错误累积。
────────────────────────────
7. 全局 metaLocks map 无限增长 → 内存泄漏
文件: internal/session/meta_state.go:17-21
问题: metaLocks 是一个 map[string]*sync.Mutex,每次遇到新 session 文件路径就添加一个条目,从不清理。长时间运行创建大量 session 后,map 持续增长。
────────────────────────────
🟡 中等级别(可能导致逻辑错误或错误用户体验)
8. externalDirs 硬编码 Unix /tmp/ 路径 → Windows 上错误判断
文件: internal/policy/policy.go:630
问题: strings.HasPrefix(clean, "/tmp/") || strings.HasPrefix(clean, "/private/tmp/") 是硬编码的 Unix 路径。Windows 上临时目录是
C:\Users...\AppData\Local\Temp\。结果:Windows 用户操作临时目录中的文件会被错误地标记为"外部目录" 而触发审批提示。
────────────────────────────
9. TUI localSubmitCommands 数据竞争(data race)
文件:
• internal/tui/model_prompt.go:146 — 在模型 goroutine 中 append 到 localSubmitCommands
• internal/tui/model_events.go:586-588 — 在事件处理 goroutine 中 pop 并 copy
问题: 两个 goroutine 同时读取/修改同一个 slice,无任何锁保护。Go 的 data race detector 会报告此问题。
────────────────────────────
10. emitBestEffort 静默丢弃关键 UI 增量数据
文件: internal/app/service/events.go:76-85
问题: flushBestEffort 使用 select/default 模式 — 当 channel 满时静默丢弃文本增量块。用户界面会缺失 assistant
的回复文本,且丢弃通知(droppedFlushes)仅在 flushReliable 时发出,而 flushReliable 可能在 turn 结束时未被调用。
────────────────────────────
11. expandUserPath 不支持 Windows 环境变量
文件: internal/app/config_file.go:510-523
问题: expandUserPath 只处理 Unix 风格的 ~。Windows 用户常见的 %USERPROFILE%、%APPDATA% 路径不会被展开。MCP 配置路径(ConfigPath)经过此函数,导致
Windows 上 MCP 配置找不到。
────────────────────────────
12. turnDeltaCoalescers.add() 无锁并发访问切片
文件: internal/app/service/events.go:56-67
问题: add() 方法在 c.chunks 上做 append 操作,没有互斥锁。多个 goroutine 同时调用 add()(来自流处理的各个阶段),导致 data race 和潜在的 slice 损坏。
────────────────────────────
13. Windows \r\n 处理在 splitShellRuleSegments 中产生空段
文件: internal/policy/policy.go:598-599
问题: 处理 \r\n 时,先遇到 \r 分割一次,再遇到 \n 又分割一次,产生空字符串段。虽然外层逻辑会过滤空值,但这是脆弱的偶然正确,未来修改可能打破。
────────────────────────────
🟢 低级别(边缘情况或样式问题)
14. overlayExplicitConfig 无法区分"未设置"和"显式设为零"
文件: internal/app/config_file.go:313-316
问题: if src.RetryMaxAttempts != 0 — 如果用户显式设置 RetryMaxAttempts = 0(禁用重试),零值被当作"未提供",静默使用默认值。
────────────────────────────
15. open_command.go:33 — 绝对路径 target 不被 workspaceRoot 约束
文件: internal/app/open_command.go:117
} else if !filepath.IsAbs(target) {
target = filepath.Join(base, target)
}
// 如果 target 已经是绝对路径(如 /etc/passwd 或 C:\Windows...),则直接使用,不检查 workspace
────────────────────────────
16. line_endings.go 中 Myers diff 回溯顺序敏感
文件: internal/tools/line_endings.go:264-300
backtrackMyersLineEndingMatches 的匹配顺序对输入 trace 高度敏感。当 Myers diff 在最大编辑次数边界失败时,会 fallback 到
expandedUniqueLineEndingMatches,其匹配质量远低于 LCS。对于混合换行符的大型文件,换行符恢复可能产生不一致的结果。
现在我已充分理解所有执行路径。以下是综合风险评估报告:
────────────────────────────
🎯 Bug 风险综合评估报告
基于对调用链、触发条件、影响范围的深度追踪,以下是每个 bug 的 实际风险评级(按严重程度从高到低):
────────────────────────────
🔴 高风险(需立即修复)
│ Bug │ 实际风险分析
──────────┼────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
#1 / #15 │ /open 路径穿越 │ 风险: 高 — 安全漏洞。/open 在 TUI 中通过 slash 命令触发,任何用户输入直接进入
│ │ ResolveOpenPath(model_prompt.go:157 → open_command.go:124)。无任何 workspace
│ │ 边界检查。攻击者可读取系统任意文件(/etc/shadow、C:\Windows\System32\config\SAM)。编辑器被启动后(tea.ExecProce
│ │ ss)文件内容直接显示在编辑器中。无需提权,仅需一个 /open ../../etc/passwd 命令。
#3 │ channel send 死锁 │ 风险: 中高 — 生产环境死锁。turn_loop.go 中 14 处、stream.go 中 28 处 out <- / events <- 没有任何 select { case
│ │ <-ctx.Done()... } 保护。实际触发路径:当 s.events channel(buffer=512)在 UI 拥堵时短暂填满,runTurnWith
│ │ 中的消费者阻塞在 emitReliable() → 无法从 events channel (buffer=16) 继续消费 → 生产者 goroutine 在 out <-
│ │ 上永久阻塞。goroutine 泄漏 + 整个 turn 挂死。修复成本低(每处加 select 保护),不做就是定时炸弹。
#2 │ shell panic → 任务泄漏 │ 风险: 中高 — 资源泄漏 + 静默失败。go func() 无 recover()(shell.go:42)。若 runShellBackground() panic(如
│ │ shell.Resolve 遇到恶意输入、空指针解引用),b.tasks.completed(task.ID) 永远不会执行。任务永久卡在 "running"
│ │ 状态,pruneCompletedLocked 明确跳过 running 任务(tasks.go:167)。每 panic
│ │ 一次泄漏一个注册表条目,后台任务结果永久丢失(shell_wait 永远返回 running)。LLM 可以构造触发 panic 的 shell
│ │ 命令。
────────────────────────────
🟡 中风险(建议近期修复)
│ Bug │ 实际风险分析
─────┼──────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
#9 │ TUI data race │ 风险: 中 — 偶发 panic / 数据损坏。model_prompt.go:146(TUI goroutine)和 model_events.go:586-588(event
│ │ goroutine)无锁同时访问 localSubmitCommands。Go data race detector 明确报告。虽然 Bubble Tea 模型字段通常只从
│ │ Update() 修改,但 popLocalSubmitCommand 和 append 在不同 goroutine 路径发生,copy + append 并发 = slice header 损坏
│ │ → panic 或静默数据丢失。
#10 │ emitBestEffort 丢数据 │ 风险: 中 — 用户可见的文本缺失。flushBestEffort(events.go:76-85)使用 select/default,channel 满时静默丢弃
│ │ EventAssistantDelta、EventReasoningDelta、EventPlanDelta、EventInfo。虽然 flushReliable 会在 turn
│ │ 结束时报告丢弃计数,但中间的文本已永久丢失。512 的 buffer 在快速流式输出 + 复杂 UI 渲染时可能填满,用户看到不完整的
│ │ AI 回复。
#8 │ externalDirs Unix 硬编码 │ 风险: 中(仅 Windows)— 功能异常。policy.go:630 硬编码 "/tmp/" 和 "/private/tmp/"。Windows 上
│ │ %TEMP%(C:\Users\AppData\Local\Temp\)路径不被排除。每个操作临时文件的 shell
│ │ 命令都触发虚假的审批提示。若用户设置了 external_directory =
│ │ "deny",临时文件操作被静默阻止,导致正常操作失败。Windows 是主要支持平台之一,影响面较大。
#4 │ os.Chdir 并发不安全 │ 风险: 中 — 隐蔽的数据损坏。worktree_exit.go:78 调用 os.Chdir() 改变进程级 CWD。在 worktree
│ │ 退出窗口(RemoveCurrentWorktree),若有并发 shell_run 使用相对路径,会在错误目录下执行。第 95 行 _ =
│ │ os.Chdir(previousCwd) 静默忽略恢复失败。触发频率低但后果严重(写错文件)。
────────────────────────────
🟢 低风险(可延迟修复或需注意)
│ Bug │ 实际风险分析
─────┼─────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────
#14 │ overlayExplicitConfig 零值 │ 风险: 低。RetryMaxAttempts != 0 用 0 做哨兵。用户若想设置 RetryMaxAttempts =
│ │ 0(禁用重试)会被忽略。当前无用户报告此用例。
#5 │ reloadPluginDisabledConfig 忽略错误 │ 风险:
│ │ 低。仅在配置损坏时触发,且后果只是插件状态异常(表现为插件意外加载/不加载),不导致崩溃或数据丢失。与
│ │ reloadSkillDisabledConfig 不一致但影响面小。
#6 │ PatchSessionMeta 不可清零 │ 风险: 低。当前调用者从不传 TotalCostUSD=0 或 TurnCount=0。只影响未来可能的"重置 session 统计"功能。
#7 │ metaLocks 内存泄漏 │ 风险: 极低。每 session 一个 *sync.Mutex(~64 bytes)。即使 10,000 sessions 也仅
│ │ ~640KB。磁盘会先于内存成为瓶颈。
#11 │ expandUserPath 不支持 Windows │ 风险: 低。MCP 配置路径中 %APPDATA% 不展开,Windows 用户拿到字面路径。易通过文档规避。
#12 │ turnDeltaCoalescers 无锁 │ 风险: 低。add() 修改 c.chunks 无锁,但在当前实现中,turnDeltaCoalescers 由 runTurnWith 独享且在单个
│ │ goroutine 中使用。未来代码演进可能引入并发。
#13 │ CRLF 双分割空段 │ 风险: 极低。空段被外层 trim 过滤,当前"偶然正确"。仅在 Windows 编辑器保存混合换行符 shell 规则时触发。
#16 │ Myers diff 编辑限制 │ 风险: 极低。大文件(>4M cells)+ 编辑距离 >1024 时 fallback。仅影响混合换行符文件的换行符恢复质量。
────────────────────────────
📊 风险矩阵总览
严重程度: 🔴 高风险 🟡 中风险 🟢 低风险
触发频率: ◆ 频繁 ◇ 偶尔 △ 罕见
Bug # Severity Likelihood Impact Overall Priority
─────────────────────────────────────────────────────────
#1/#15 🔴 高 ◆ 频繁 读取任意文件 ⭐ P0-紧急
#3 🔴 高 ◇ 偶尔 goroutine 死锁 ⭐ P0-紧急
#2 🔴 高 ◇ 偶尔 任务泄漏+丢失 ⭐ P1-尽快
#9 🟡 中 ◇ 偶尔 panic/数据损坏 ⭐ P2-中期
#10 🟡 中 ◇ 偶尔 文本丢失 ⭐ P2-中期
#8 🟡 中 ◆ 频繁(Win) 虚假审批/功能阻 ⭐ P2-中期
#4 🟡 中 △ 罕见 数据损坏 ⭐ P2-中期
#14 🟢 低 △ 罕见 配置不生效 ⭐ P3-待处理
#5 🟢 低 △ 罕见 插件状态异常 ⭐ P3-待处理
#6 🟢 低 △ 罕见 未来功能限制 ⭐ P3-待处理
#7 🟢 低 △ 罕见 无实际影响 ⭐ P4-已知
#11 🟢 低 ◇ 偶尔(Win) 路径不展开 ⭐ P3-待处理
#12 🟢 低 △ 罕见 未来隐患 ⭐ P3-待处理
#13 🟢 低 △ 罕见 偶然正确 ⭐ P4-已知
#16 🟢 低 △ 罕见 换行恢复降质 ⭐ P4-已知
⚡ 建议修复顺序
立即(P0): Bug remove 'scroll/copy with terminal' footer text #1 /Fix/windows tui clear screen #15 (路径穿越 — 安全漏洞)+ Bug feat: rename Chat mode to Agent mode, add Agent system prompt, remove /agent slash command #3 (channel 死锁 — 稳定性)
尽快(P1): Bug fix(tui): sync status bar model/effort/thinking with config on startup #2 (goroutine 加 recover() + 确保 completed() 执行)
中期(P2): Bug fix: stop assuming Linux on Windows — align shell selection with OS r… #9 (加锁)、[bug] windows support bugs #10 (增大 buffer 或改用可靠的 flush)、[bug] Agent always uses Linux commands on Windows, ignoring the actual operating system environment #8 (用 os.TempDir() 替代硬编码路径)、[bug] The output is too long and goes off the screen — can't scroll up to see it. #4 (用 atomic 加锁保护 Chdir)
待处理(P3): Bug [feature] SSD Multi-Agent Cooperation for Large-Scale Repository Engineering #5 、[bug] bash can't be use in windows #6 、feat: Windows PowerShell adaptation #11 、Fix/windows critical bugs #12 、Fix/windows process tree kill #14
已知(P4): Bug feat(shell): auto-detect shell binary on Windows #7 、Fix/windows build ps1 #13 、Fix/windows crlf file tools #16
🐛 Whale 程序 Bug 分析报告
经过对 25+ 核心文件的深入审查,发现了以下 严重、中等、轻微 三个级别的 Bug:
────────────────────────────
🔴 严重级别(可能导致崩溃、数据损坏或安全漏洞)
文件: internal/app/open_command.go:124-132 — 函数 ResolveOpenPath
问题: /open ../../etc/passwd 可以打开工作区以外的任意系统文件。与所有其他文件工具(read_file、edit、write 等)不同,该函数没有任何工作区边界检查 —
没有调用 safePath/safeWorkspacePath。
// ❌ 缺少: if !strings.HasPrefix(abs, workspaceRoot) { return error }
────────────────────────────
2. 后台 shell goroutine panic 导致任务泄漏
文件: internal/tools/shell.go:49-54 → 调用 internal/tools/tasks.go:83-90
问题: 若 runShellBackground() 中 panic(如 shell.Resolve 遇到 nil 指针),b.tasks.completed(task.ID) 永远不会被调用。任务永远卡在 "running"
状态,清理逻辑只删除非 running 状态的任务 — 每 panic 一次就泄漏一个注册表条目。
────────────────────────────
3. turn_loop.go 中 channel send 无 context 保护,存在死锁风险
文件: internal/agent/turn_loop.go:78,87,100,117,119,143,145,155,161,163,170
问题: out <- AgentEvent{...} 在超过 15 个地方被执行,均没有用 select { case <-ctx.Done(): ...; case out <- ev: } 保护。如果消费者停止读取
channel(context
取消或不再 drain),goroutine 永久阻塞 — goroutine 泄漏 + 死锁。
────────────────────────────
4. os.Chdir 非 goroutine-safe 导致工作目录混乱
文件: internal/app/worktree_exit.go:76-78,96
问题: os.Chdir() 修改的是进程级工作目录。在并发场景下(或 hook 回调中),其他 goroutine 使用相对路径会静默地使用错误目录。且第 96 行忽略
os.Chdir(previousCwd) 错误:
────────────────────────────
5. reloadPluginDisabledConfig 静默忽略错误
文件: internal/app/commands.go:636-639 vs commands.go:624-627
问题: reloadPluginDisabledConfig 调用了 ApplyLoadedConfig(&cfg, loaded) 但丢弃了错误返回值。如果项目配置无效,PluginsDisabled
会被静默地用错误数据更新。对比 reloadSkillDisabledConfig(第 624 行)正确处理了错误。
────────────────────────────
6. PatchSessionMeta 无法将 TotalCostUSD/TurnCount 清零
文件: internal/session/meta_state.go:130-140
问题: 条件 if patch.TotalCostUSD != 0 { cur.TotalCostUSD = patch.TotalCostUSD } 用 0
作为"不修改"的哨兵值,导致一旦累积了费用或轮次,就无法重置回零。恢复旧 session 时成本数据会持续错误累积。
────────────────────────────
7. 全局 metaLocks map 无限增长 → 内存泄漏
文件: internal/session/meta_state.go:17-21
问题: metaLocks 是一个 map[string]*sync.Mutex,每次遇到新 session 文件路径就添加一个条目,从不清理。长时间运行创建大量 session 后,map 持续增长。
────────────────────────────
🟡 中等级别(可能导致逻辑错误或错误用户体验)
8. externalDirs 硬编码 Unix /tmp/ 路径 → Windows 上错误判断
文件: internal/policy/policy.go:630
问题: strings.HasPrefix(clean, "/tmp/") || strings.HasPrefix(clean, "/private/tmp/") 是硬编码的 Unix 路径。Windows 上临时目录是
C:\Users...\AppData\Local\Temp\。结果:Windows 用户操作临时目录中的文件会被错误地标记为"外部目录" 而触发审批提示。
────────────────────────────
9. TUI localSubmitCommands 数据竞争(data race)
文件:
• internal/tui/model_prompt.go:146 — 在模型 goroutine 中 append 到 localSubmitCommands
• internal/tui/model_events.go:586-588 — 在事件处理 goroutine 中 pop 并 copy
问题: 两个 goroutine 同时读取/修改同一个 slice,无任何锁保护。Go 的 data race detector 会报告此问题。
────────────────────────────
10. emitBestEffort 静默丢弃关键 UI 增量数据
文件: internal/app/service/events.go:76-85
问题: flushBestEffort 使用 select/default 模式 — 当 channel 满时静默丢弃文本增量块。用户界面会缺失 assistant
的回复文本,且丢弃通知(droppedFlushes)仅在 flushReliable 时发出,而 flushReliable 可能在 turn 结束时未被调用。
────────────────────────────
11. expandUserPath 不支持 Windows 环境变量
文件: internal/app/config_file.go:510-523
问题: expandUserPath 只处理 Unix 风格的 ~。Windows 用户常见的 %USERPROFILE%、%APPDATA% 路径不会被展开。MCP 配置路径(ConfigPath)经过此函数,导致
Windows 上 MCP 配置找不到。
────────────────────────────
12. turnDeltaCoalescers.add() 无锁并发访问切片
文件: internal/app/service/events.go:56-67
问题: add() 方法在 c.chunks 上做 append 操作,没有互斥锁。多个 goroutine 同时调用 add()(来自流处理的各个阶段),导致 data race 和潜在的 slice 损坏。
────────────────────────────
13. Windows \r\n 处理在 splitShellRuleSegments 中产生空段
文件: internal/policy/policy.go:598-599
问题: 处理 \r\n 时,先遇到 \r 分割一次,再遇到 \n 又分割一次,产生空字符串段。虽然外层逻辑会过滤空值,但这是脆弱的偶然正确,未来修改可能打破。
────────────────────────────
🟢 低级别(边缘情况或样式问题)
14. overlayExplicitConfig 无法区分"未设置"和"显式设为零"
文件: internal/app/config_file.go:313-316
问题: if src.RetryMaxAttempts != 0 — 如果用户显式设置 RetryMaxAttempts = 0(禁用重试),零值被当作"未提供",静默使用默认值。
────────────────────────────
15. open_command.go:33 — 绝对路径 target 不被 workspaceRoot 约束
文件: internal/app/open_command.go:117
}
// 如果 target 已经是绝对路径(如 /etc/passwd 或 C:\Windows...),则直接使用,不检查 workspace
────────────────────────────
16. line_endings.go 中 Myers diff 回溯顺序敏感
文件: internal/tools/line_endings.go:264-300
backtrackMyersLineEndingMatches 的匹配顺序对输入 trace 高度敏感。当 Myers diff 在最大编辑次数边界失败时,会 fallback 到
expandedUniqueLineEndingMatches,其匹配质量远低于 LCS。对于混合换行符的大型文件,换行符恢复可能产生不一致的结果。
现在我已充分理解所有执行路径。以下是综合风险评估报告:
────────────────────────────
🎯 Bug 风险综合评估报告
基于对调用链、触发条件、影响范围的深度追踪,以下是每个 bug 的 实际风险评级(按严重程度从高到低):
────────────────────────────
🔴 高风险(需立即修复)
│ Bug │ 实际风险分析
──────────┼────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
#1 / #15 │ /open 路径穿越 │ 风险: 高 — 安全漏洞。/open 在 TUI 中通过 slash 命令触发,任何用户输入直接进入
│ │ ResolveOpenPath(model_prompt.go:157 → open_command.go:124)。无任何 workspace
│ │ 边界检查。攻击者可读取系统任意文件(/etc/shadow、C:\Windows\System32\config\SAM)。编辑器被启动后(tea.ExecProce
│ │ ss)文件内容直接显示在编辑器中。无需提权,仅需一个 /open ../../etc/passwd 命令。
#3 │ channel send 死锁 │ 风险: 中高 — 生产环境死锁。turn_loop.go 中 14 处、stream.go 中 28 处 out <- / events <- 没有任何 select { case
│ │ <-ctx.Done()... } 保护。实际触发路径:当 s.events channel(buffer=512)在 UI 拥堵时短暂填满,runTurnWith
│ │ 中的消费者阻塞在 emitReliable() → 无法从 events channel (buffer=16) 继续消费 → 生产者 goroutine 在 out <-
│ │ 上永久阻塞。goroutine 泄漏 + 整个 turn 挂死。修复成本低(每处加 select 保护),不做就是定时炸弹。
#2 │ shell panic → 任务泄漏 │ 风险: 中高 — 资源泄漏 + 静默失败。go func() 无 recover()(shell.go:42)。若 runShellBackground() panic(如
│ │ shell.Resolve 遇到恶意输入、空指针解引用),b.tasks.completed(task.ID) 永远不会执行。任务永久卡在 "running"
│ │ 状态,pruneCompletedLocked 明确跳过 running 任务(tasks.go:167)。每 panic
│ │ 一次泄漏一个注册表条目,后台任务结果永久丢失(shell_wait 永远返回 running)。LLM 可以构造触发 panic 的 shell
│ │ 命令。
────────────────────────────
🟡 中风险(建议近期修复)
│ Bug │ 实际风险分析
─────┼──────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
#9 │ TUI data race │ 风险: 中 — 偶发 panic / 数据损坏。model_prompt.go:146(TUI goroutine)和 model_events.go:586-588(event
│ │ goroutine)无锁同时访问 localSubmitCommands。Go data race detector 明确报告。虽然 Bubble Tea 模型字段通常只从
│ │ Update() 修改,但 popLocalSubmitCommand 和 append 在不同 goroutine 路径发生,copy + append 并发 = slice header 损坏
│ │ → panic 或静默数据丢失。
#10 │ emitBestEffort 丢数据 │ 风险: 中 — 用户可见的文本缺失。flushBestEffort(events.go:76-85)使用 select/default,channel 满时静默丢弃
│ │ EventAssistantDelta、EventReasoningDelta、EventPlanDelta、EventInfo。虽然 flushReliable 会在 turn
│ │ 结束时报告丢弃计数,但中间的文本已永久丢失。512 的 buffer 在快速流式输出 + 复杂 UI 渲染时可能填满,用户看到不完整的
│ │ AI 回复。
#8 │ externalDirs Unix 硬编码 │ 风险: 中(仅 Windows)— 功能异常。policy.go:630 硬编码 "/tmp/" 和 "/private/tmp/"。Windows 上
│ │ %TEMP%(C:\Users\AppData\Local\Temp\)路径不被排除。每个操作临时文件的 shell
│ │ 命令都触发虚假的审批提示。若用户设置了 external_directory =
│ │ "deny",临时文件操作被静默阻止,导致正常操作失败。Windows 是主要支持平台之一,影响面较大。
#4 │ os.Chdir 并发不安全 │ 风险: 中 — 隐蔽的数据损坏。worktree_exit.go:78 调用 os.Chdir() 改变进程级 CWD。在 worktree
│ │ 退出窗口(RemoveCurrentWorktree),若有并发 shell_run 使用相对路径,会在错误目录下执行。第 95 行 _ =
│ │ os.Chdir(previousCwd) 静默忽略恢复失败。触发频率低但后果严重(写错文件)。
────────────────────────────
🟢 低风险(可延迟修复或需注意)
│ Bug │ 实际风险分析
─────┼─────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────
#14 │ overlayExplicitConfig 零值 │ 风险: 低。RetryMaxAttempts != 0 用 0 做哨兵。用户若想设置 RetryMaxAttempts =
│ │ 0(禁用重试)会被忽略。当前无用户报告此用例。
#5 │ reloadPluginDisabledConfig 忽略错误 │ 风险:
│ │ 低。仅在配置损坏时触发,且后果只是插件状态异常(表现为插件意外加载/不加载),不导致崩溃或数据丢失。与
│ │ reloadSkillDisabledConfig 不一致但影响面小。
#6 │ PatchSessionMeta 不可清零 │ 风险: 低。当前调用者从不传 TotalCostUSD=0 或 TurnCount=0。只影响未来可能的"重置 session 统计"功能。
#7 │ metaLocks 内存泄漏 │ 风险: 极低。每 session 一个 *sync.Mutex(~64 bytes)。即使 10,000 sessions 也仅
│ │ ~640KB。磁盘会先于内存成为瓶颈。
#11 │ expandUserPath 不支持 Windows │ 风险: 低。MCP 配置路径中 %APPDATA% 不展开,Windows 用户拿到字面路径。易通过文档规避。
#12 │ turnDeltaCoalescers 无锁 │ 风险: 低。add() 修改 c.chunks 无锁,但在当前实现中,turnDeltaCoalescers 由 runTurnWith 独享且在单个
│ │ goroutine 中使用。未来代码演进可能引入并发。
#13 │ CRLF 双分割空段 │ 风险: 极低。空段被外层 trim 过滤,当前"偶然正确"。仅在 Windows 编辑器保存混合换行符 shell 规则时触发。
#16 │ Myers diff 编辑限制 │ 风险: 极低。大文件(>4M cells)+ 编辑距离 >1024 时 fallback。仅影响混合换行符文件的换行符恢复质量。
────────────────────────────
📊 风险矩阵总览
严重程度: 🔴 高风险 🟡 中风险 🟢 低风险
触发频率: ◆ 频繁 ◇ 偶尔 △ 罕见
⚡ 建议修复顺序