Files
Nexus/.cursor/skills/systematic-debugging/root-cause-tracing.md
T
Your Name cdd0be328a fix: 六轮深度扫描 — 47项Bug修复、安全加固、死代码清理
Critical runtime bugs:
- terminal.html WebSSH完全不可用(URL前缀/JSON解析/Content-Type三处错误)
- servers.py路由遮蔽:/logs被/{id}拦截,3个前端页面同步日志查询失败
- scripts.html startExecPoll()→startExecPolling(),长任务快速执行崩溃
- agent.py {value!r!s:.50}格式串非法,agent发非数值时ValueError
- alerts.html d.daily.reduce()无null检查,API返回空数据时TypeError

Resource leak / stability:
- websocket.py僵尸连接未关闭TCP,文件描述符泄漏
- websocket.py _last_alert_time字典无限增长(加1小时过期清理)
- asyncssh_pool.py全忙时超过MAX_CONNECTIONS无限增长
- self_monitor.py Telegram告警无冷却,宕机时每30秒刷屏
- schedule_runner.py一次性调度执行超60秒会重复触发
- 限速脚本EXPIRE每次重置窗口可绕过(改用Lua原子脚本)

Security:
- JWT access token加token_version声明,改密码后旧token立失效(零宽限)
- INSTALL_MODE导入时常量→动态函数,安装后JWT认证不再残留禁用
- install.py /lock端点加管理员存在性验证,防止阻断安装
- ServerUpdate schema移除connectivity只读字段,防止伪造连接状态

Frontend fixes:
- doExec()缺r.ok检查、commands.html null检查
- _server_to_dict()补last_checked_at+ssh_key_public
- _field_match()逗号cron表达式修复
- alerts类型显示、SSH会话名称、搜索高亮定位
- 一次性/循环定时任务(run_mode+fire_at+自动禁用)

Dead code removed (400+ lines):
- SyncService batch_push/_push_single等5个方法(零调用者)
- 5个未使用schema(SyncCommands/SyncConfig/SyncSftp/FileDeploy/PaginatedResponse)
- 6个零调用service方法、3个无前端API端点
- 4个未使用import

Schema migrations:
- push_schedules: run_mode + fire_at列,cron_expr改NULL
- servers: 7个新列 + ssh_key_private/public VARCHAR(500)→TEXT

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-24 16:26:40 +08:00

5.3 KiB
Raw Blame History

根因追踪

概述

Bug 通常表现在调用栈深处(在错误目录执行 git init、在错误位置创建文件、用错误路径打开数据库)。你的本能是在错误出现的地方修复,但那只是治标。

核心原则: 沿着调用链反向追踪,直到找到最初的触发点,然后在源头修复。

何时使用

digraph when_to_use {
    "Bug 出现在调用栈深处?" [shape=diamond];
    "能反向追踪吗?" [shape=diamond];
    "在症状处修复" [shape=box];
    "追踪到最初的触发点" [shape=box];
    "更好的做法:同时添加纵深防御" [shape=box];

    "Bug 出现在调用栈深处?" -> "能反向追踪吗?" [label="是"];
    "能反向追踪吗?" -> "追踪到最初的触发点" [label="是"];
    "能反向追踪吗?" -> "在症状处修复" [label="否——死胡同"];
    "追踪到最初的触发点" -> "更好的做法:同时添加纵深防御";
}

适用场景:

  • 错误发生在执行深处(不在入口点)
  • 堆栈跟踪显示很长的调用链
  • 不清楚无效数据从哪里来
  • 需要找到是哪个测试/代码触发了问题

追踪流程

1. 观察症状

Error: git init failed in /Users/jesse/project/packages/core

2. 找到直接原因

哪段代码直接导致了这个错误?

await execFileAsync('git', ['init'], { cwd: projectDir });

3. 问:谁调用了它?

WorktreeManager.createSessionWorktree(projectDir, sessionId)
    Session.initializeWorkspace() 调用
    Session.create() 调用
   被测试中的 Project.create() 调用

4. 继续向上追踪

传入了什么值?

  • projectDir = ''(空字符串!)
  • 空字符串作为 cwd 会解析为 process.cwd()
  • 那就是源代码目录!

5. 找到最初的触发点

空字符串从哪里来的?

const context = setupCoreTest(); // 返回 { tempDir: '' }
Project.create('name', context.tempDir); // 在 beforeEach 之前就访问了!

添加堆栈跟踪

当无法手动追踪时,添加诊断埋点:

// 在有问题的操作之前
async function gitInit(directory: string) {
  const stack = new Error().stack;
  console.error('DEBUG git init:', {
    directory,
    cwd: process.cwd(),
    nodeEnv: process.env.NODE_ENV,
    stack,
  });

  await execFileAsync('git', ['init'], { cwd: directory });
}

重要: 在测试中使用 console.error()(而非 logger——可能不会显示)

运行并捕获:

npm test 2>&1 | grep 'DEBUG git init'

分析堆栈跟踪:

  • 找测试文件名
  • 找触发调用的行号
  • 识别模式(同一个测试?同一个参数?)

找出导致污染的测试

如果某些现象在测试期间出现,但你不知道是哪个测试造成的:

使用本目录下的二分查找脚本 find-polluter.sh

./find-polluter.sh '.git' 'src/**/*.test.ts'

逐个运行测试,在第一个"污染者"处停止。详见脚本中的使用说明。

真实案例:空的 projectDir

症状: .git 被创建在 packages/core/(源代码目录)中

追踪链:

  1. git initprocess.cwd() 中执行 ← cwd 参数为空
  2. WorktreeManager 被传入空的 projectDir
  3. Session.create() 传递了空字符串
  4. 测试在 beforeEach 之前访问了 context.tempDir
  5. setupCoreTest() 初始返回 { tempDir: '' }

根本原因: 顶层变量初始化时访问了空值

修复: 将 tempDir 改为 getter,在 beforeEach 之前访问时抛出异常

同时添加了纵深防御:

  • 第 1 层:Project.create() 校验目录
  • 第 2 层:WorkspaceManager 校验非空
  • 第 3 层:NODE_ENV 守卫拒绝在 tmpdir 之外执行 git init
  • 第 4 层:git init 前记录堆栈跟踪

关键原则

digraph principle {
    "找到了直接原因" [shape=ellipse];
    "能向上追踪一层吗?" [shape=diamond];
    "反向追踪" [shape=box];
    "这就是源头吗?" [shape=diamond];
    "在源头修复" [shape=box];
    "在每一层添加校验" [shape=box];
    "Bug 不可能再发生" [shape=doublecircle];
    "绝不只修症状" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];

    "找到了直接原因" -> "能向上追踪一层吗?";
    "能向上追踪一层吗?" -> "反向追踪" [label="是"];
    "能向上追踪一层吗?" -> "绝不只修症状" [label="否"];
    "反向追踪" -> "这就是源头吗?";
    "这就是源头吗?" -> "反向追踪" [label="否——继续追踪"];
    "这就是源头吗?" -> "在源头修复" [label="是"];
    "在源头修复" -> "在每一层添加校验";
    "在每一层添加校验" -> "Bug 不可能再发生";
}

绝不只在错误出现的地方修复。 反向追踪,找到最初的触发点。

堆栈跟踪技巧

在测试中: 使用 console.error() 而非 logger——logger 可能被抑制 操作之前: 在危险操作之前记录日志,而不是在失败之后 包含上下文: 目录、cwd、环境变量、时间戳 捕获堆栈: new Error().stack 能显示完整的调用链

实际效果

来自调试实践(2025-10-03):

  • 通过 5 层追踪找到了根本原因
  • 在源头修复(getter 校验)
  • 添加了 4 层纵深防御
  • 1847 个测试通过,零污染