普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月6日微言 | wyanassert

大型 iOS 项目的简单 bug 自动修复实践

作者 wyanassert
2026年3月6日 22:26

工具概述

iOS Bug AutoFix 是一个基于 AI 的 iOS 代码 Bug 自动定位工具。它从自然语言 Bug 描述出发,通过三步流水线(信息提取 → 粗筛定位 → 精确定位)自动定位到问题代码的具体文件和行号。本次分析以两条实际命令的运行为例。


命令一:index — 构建代码索引

执行命令

1
npx ts-node src/index.ts index

加载配置

入口文件 index.tsmain() 函数首先调用 loadConfig() 读取配置文件:

  • 配置路径: tool/config/autofix.config.json
  • 读取结果:
    • repoRoot/Users/wyan/Develop/Code/branch/Bugfix
    • openai.modeldeepseek-chat
    • index.includeDirs["Classes/Modules"]

同时在构造 BugAutoFixer 时,基于 repoRoot 设置了运行时目录:

  • .autofix/ 根目录
  • .autofix/index.db — SQLite 索引数据库
  • .autofix/results/ — 定位结果目录
  • .autofix/logs/ — 日志目录(预留)

加载页面映射表

BugAutoFixer 构造函数中创建 FileLocator,而 FileLocator 构造时会创建 PageMapperpage-mapper.ts 会按优先级搜索 page-mapping.json 文件:

1
✓ 已加载页面映射表: .../page-mapping.json (14 个页面)

映射表内容示例(来自 page-mapping.example.json):

1
2
3
4
5
{
"个人主页": ["QMPersonalInfoViewController", "QMGeneralUserHeaderView", "QMGeneralUserV2TabVC"],
"播放页": ["QMAudioPlayerVC", "QMPlayingSongPage", ...],
...
}

页面映射表同时构建了反向映射(类名 → 页面名),共 14 个页面。

索引构建流程

code-indexer.tsbuildFullIndex() 方法执行以下步骤:

数据库初始化

创建 SQLite 数据库(WAL 模式),包含:

用途
file_index 文件级索引(类名、方法名、协议、UI 类、无障碍标记等)
class_hierarchy 类继承关系
file_fts (FTS5) 全文搜索虚拟表,通过触发器自动同步

扫描源文件

使用 find 命令扫描仓库,由于配置了 includeDirs: ["Classes/Modules"],实际执行的命令相当于:

1
find "/Users/wyan/Develop/Code/branch/Bugfix" -type f \( -name "*.swift" -o -name "*.m" -o -name "*.h" \) -and \( -path "*/Classes/Modules/*" \)
1
Found 17522 source files to index

逐文件解析

在一个 SQLite 事务中,对每个文件进行解析。根据文件扩展名分别调用:

  • .swift 文件parseSwiftFile(): 用正则提取 class/struct/enum/extension 声明、func 方法名、协议、UI* 类使用、accessibility* 属性、@IBOutlet
  • .m / .h 文件parseObjCFile(): 用正则提取 @interface/@implementation(含 Category)、方法名(-/+ (type)methodName)、<Protocol> 协议、UI* 类指针声明、accessibility* 属性

每个文件还会:

  1. 生成raw_summary :取前 30 行 + 所有关键声明行(class/func/@interface/@implementation/accessibility 等),控制在 2000 字符以内
  2. 推断 pod_name :从路径中匹配 Pods/ModuleName/Modules/ModuleName/ 模式
  3. 提取类继承关系 :存入 class_hierarchy

FTS5 全文索引自动同步

FTS5 是 Full-Text Search version 5 的缩写,即 SQLite 内置的第 5 版全文搜索引擎。
本项目用它来对 17522 个源文件的类名、方法名等元数据建立倒排索引,让 Step 2 的关键词搜索可以在毫秒级完成。

通过 SQLite 触发器,file_index 表的 INSERT/UPDATE/DELETE 操作会自动同步到 file_fts 全文搜索虚拟表,支持后续的 MATCH 全文搜索。

最终结果

1
2
Indexed: 17522, Skipped: 0
Index built successfully!

17522 个源文件全部成功索引。


命令二:locate — 定位 Bug

执行命令

1
npx ts-node src/index.ts locate "个人主页导航栏更多按钮无障碍响应错误"

整个 locate 流程分为三个 Step,总耗时 73.7 秒


Step 1: 信息提取(LLM 调用 #1)

执行者: bug-info-extractor.ts

构建 Prompt

将 bug 描述嵌入一个结构化 prompt 中,要求 LLM 以 JSON 格式输出提取结果。Prompt 关键指令:

“keywords 要包含各种可能的命名变体,比如中文’播放页’对应可能的类名 PlayerViewController, PlayViewController, PlayerVC…”

调用 DeepSeek API

使用 OpenAI SDK 的 chat.completions.create

  • 模型: deepseek-chat
  • 温度: 0.1(低温度确保输出稳定)
  • 响应格式: json_object(强制 JSON 输出)
  • 重试机制: 最多 3 次,指数退避(1s → 2s → 4s)

LLM 返回结果(解析后)

1
2
3
4
5
6
7
8
9
10
11
Type:       accessibility
Summary: 个人主页导航栏更多按钮的无障碍响应功能存在错误
Keywords: ProfileViewController, ProfileVC, PersonalHomeViewController,
HomeViewController, NavigationBar, NavBar, MoreButton, MoreBtn,
RightBarButtonItem, UIBarButtonItem, accessibilityLabel,
accessibilityHint, accessibilityTraits, isAccessibilityElement,
ProfileModule, UserProfile, PersonalCenter
Module: 个人主页/用户资料
Page: 个人主页
VCs: ProfileViewController, PersonalHomeViewController,
UserProfileViewController, HomeViewController

关键观察:LLM 从简短的中文描述中猜测了大量可能的英文类名/属性名变体,这些关键词将在 Step 2 中被用于多策略搜索。


Step 2: 粗筛定位(纯本地,无 LLM 调用)

执行者: file-locator.ts

6 种策略全部并行执行Promise.allSettled),互不影响:

策略 1: 直接路径匹配

  • 逻辑:检查 bugInfo.codeScanIssue?.filePath 是否存在
  • 本次结果:无(bug 描述中没有直接给出文件路径)
  • 权重:100 分(未触发)

策略 2: ripgrep 全文搜索(异步并行)

  • 逻辑:对 keywords 中长度 ≥ 3 的关键词,逐个并行执行 ripgrep:
    1
    rg -l --type swift --type objc "ProfileViewController" "/Users/wyan/Develop/Code/branch/Bugfix" 2>/dev/null | head -50
  • 本次匹配到的关键词(从结果中可以看到):
    • NavBar → 匹配到 QMPersonalInfoViewController.m, QMGeneralUserHeaderView.m
    • MoreButton → 匹配到 QMPersonTitleView.m, QMPersonHeaderCell.m, QMPersonalInfoViewController.m
    • MoreBtn → 匹配到多个文件
    • accessibilityHint → 匹配到 QMPersonalInfoViewController.m
    • accessibilityTraits → 匹配到 QMPersonTitleView.m, QMPersonHeaderCell.m, QMPersonalInfoViewController.m
    • ProfileViewController → 匹配到 ProfileViewController_V3Pad.m, ProfileViewController_V3+Follow.m
    • ProfileVC → 匹配到多个 Profile 相关文件
    • UserProfile → 匹配到 QMPersonalInfoViewController.m, QMPersonalInfoViewController+JumpAction.m
  • 每个匹配得 6 分

策略 3: 数据库索引查询

  • 页面映射匹配(最高权重 40 分):

    • bugInfo.pageName = "个人主页"
    • 查映射表 → ["QMPersonalInfoViewController", "QMGeneralUserHeaderView", "QMGeneralUserV2TabVC"]
    • SQL: SELECT file_path FROM file_index WHERE class_names LIKE '%QMPersonalInfoViewController%'
    • 匹配到所有 QMPersonalInfoViewController.m/.h 及 Category 文件,每个 40 分
  • 类名 FTS5 匹配(30 分):

    • viewControllers 列表(ProfileViewController, PersonalHomeViewController 等)执行全文搜索
    • SQL: SELECT file_path FROM file_fts WHERE class_names MATCH 'ProfileViewController' LIMIT 30
    • 匹配到 ProfileViewController_V3Pad.m 等文件,每个 30 分
  • 关键词 FTS5 匹配(8 分):

    • 对长度 ≥ 4 的关键词(如 MoreBtn, accessibilityLabel, accessibilityTraits, isAccessibilityElement)执行全文搜索
    • 匹配到 QMPersonTitleView.m, QMPersonHeaderCell.m

策略 4: 目录结构推断

  • 逻辑:对 pageName(”个人主页”)和 moduleName(”个人主页/用户资料”)执行 find 命令搜索匹配的目录
  • 由于中文名和目录命名不匹配,本次可能未产生有效结果

策略 5: Git 修改热点

  • 逻辑
    1
    git log --since="2 weeks ago" --name-only --pretty=format: | sort | uniq -c | sort -rn | head -100
  • 获取最近 2 周频繁修改的文件,每个 2 分
  • 低权重兜底策略

策略 6: Bug 类型专项搜索

  • bugType = “accessibility” → 调用 searchAccessibilityIssues()
  • 逻辑:在索引中查找包含特定 UI 元素但缺少无障碍属性的文件
    1
    SELECT file_path FROM file_index WHERE has_accessibility = 0 AND ui_classes LIKE '%UIButton%' LIMIT 30
  • 每个匹配 15 分

分数合并与交叉验证加分

所有策略结果通过 candidateMap 合并。同一文件多次命中的分数会叠加

关键的交叉验证加分机制

1
2
// 命中策略数 > 1 时,每多一种策略额外加 5 分
const bonus = extraStrategies * 5;

例如 QMPersonalInfoViewController.m

  • 策略 2 (ripgrep): 匹配了 NavBar, MoreButton, MoreBtn, accessibilityHint, accessibilityTraits, UserProfile → 6×6 = 36 分
  • 策略 3 (索引): 页面映射 40 分
  • 交叉验证加分: 2 种策略命中 → +5 分
  • 总分: 81 分(排名第 1)

最终排序输出 Top 20

结果按 score 降序排序,取前 MAX_CANDIDATES = 20 个文件:

排名 分数 文件 主要得分来源
1 81 QMPersonalInfoViewController.m ripgrep(6项) + 页面映射 + 交叉验证
2 57 QMPersonalInfoViewController+JumpAction.m ripgrep(ProfileVC,UserProfile) + 页面映射 + 交叉验证
3 55 ProfileViewController_V3Pad.m ripgrep + 索引类名 + 索引关键词 + 交叉验证
4 55 ProfileViewController_V3+Follow.m 同上
5 55 QMPersonTitleView.m ripgrep(MoreButton,MoreBtn,accessibilityTraits) + 索引关键词(多个) + 交叉验证
6 55 QMPersonHeaderCell.m 同上

Step 3: 精确定位(LLM 调用 #2 ~ #7)

执行者: precise-locator.ts

这是整个流程中消耗 token 最多的阶段,通过漏斗式两轮筛选来控制成本。

读取文件内容 + 生成摘要

对 Top 10(MAX_SCREENING_FILES = 10)候选文件,调用 loadFileSummaries()

  1. 读取完整文件内容fs.readFileSync(filePath, "utf-8")
  2. 生成摘要extractSummary(content) — 取前 30 行 + 所有关键声明行(class/func/@interface/@implementation/accessibility 等),约控制在 ~500 token/文件
1
2
3
4
5
6
7
8
private extractSummary(content: string): string {
const importantLines = lines.filter(line => {
return /^(class |struct |func |@interface|@implementation|@IBOutlet|@IBAction|import |#import)/.test(trimmed)
|| /accessibility/i.test(trimmed);
});
const header = lines.slice(0, 30).join("\n");
return `${header}\n\n// === Key declarations ===\n${keyDeclarations}`;
}

第一轮:摘要筛选(LLM 调用 #2)

目的:用低 token 成本快速排除无关文件。

构建 Prompt:将 bug 描述 + 10 个文件的摘要和匹配原因拼接成一个 prompt:

1
2
3
4
5
6
7
8
9
10
11
12
13
你是 iOS 开发专家。以下是一个 bug 的描述和几个候选文件的摘要。
请判断哪些文件最可能包含问题代码,返回文件路径列表(按可能性从高到低排序)。

Bug 描述:个人主页导航栏更多按钮无障碍响应错误

候选文件:
--- /path/to/QMPersonalInfoViewController.m ---
匹配原因: ripgrep 匹配关键词: NavBar, MoreButton, ...
摘要:
[前30行 + 关键声明]

--- /path/to/QMPersonTitleView.m ---
...

LLM 返回:JSON 格式的相关文件列表

1
{ "relevantFiles": ["path1", "path2", "path3", "path4", "path5"] }

结果:从 10 个文件筛选到 5 个真正相关的文件。

1
2
Round 1: Screening with file summaries...
Screened to 5 relevant files

“关键声明”是什么

在这个工具中,**”关键声明”(Key Declarations)** 是指源代码中以特定模式开头的、具有结构性意义的代码行。具体来说,就是通过正则表达式匹配出的以下内容:

匹配规则

precise-locator.tsextractSummary 方法(第 371 行)中:

1
2
3
4
5
6
7
const importantLines = lines.filter((line) => {
const trimmed = line.trim();
return (
/^(class |struct |enum |extension |func |@interface|@implementation|@IBOutlet|@IBAction|import |#import)/.test(trimmed)
|| /accessibility/i.test(trimmed)
);
});

也就是说,关键声明行 = 匹配以下任一模式的代码行:

模式 含义 示例
class Swift 类声明 class MyViewController: UIViewController
struct Swift 结构体声明 struct Config { ... }
enum 枚举声明 enum State { ... }
extension Swift 扩展声明 extension UIView { ... }
func Swift 函数声明 func viewDidLoad() { ... }
@interface ObjC 类/分类声明 @interface QMPersonalInfoViewController
@implementation ObjC 实现声明 @implementation QMPersonTitleView
@IBOutlet Storyboard 关联 @IBOutlet weak var moreBtn: UIButton!
@IBAction Storyboard 事件 @IBAction func didClickMore()
import / #import 导入语句 #import "QMPersonalInfoViewController.h"
/accessibility/i 任何包含 accessibility 的行 moreBtn.accessibilityLabel = @"更多";

摘要的组成结构

最终生成的摘要格式为:

1
2
3
4
[文件前 30 行原文]

// === Key declarations ===
[所有关键声明行]

用一个具体例子来说明,对于 QMPersonTitleView.m,摘要大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
// 前 30 行(包含 #import、文件注释等)
#import "QMPersonTitleView.h"
#import "UIView+Frame.h"
...

// === Key declarations ===
@implementation QMPersonTitleView
- (void)addMoreBtnWithTitle:... // ← func/method 声明
@IBOutlet ... // ← IBOutlet
moreBtn.accessibilityLabel = moreBtnTitle; // ← accessibility 相关
moreBtn.accessibilityTraits &= ~UIAccessibilityTraitSelected;
moreBtn.accessibilityLabel = @"更多";

为什么这么设计

这个设计的目的是用极少的 token(约 500 token/文件)让 AI 快速理解一个文件的”骨架”:

  1. 前 30 行 → 了解文件是什么(import 了什么、类名是什么)
  2. 关键声明行 → 了解文件做了什么(有哪些类、方法、UI 关联)
  3. accessibility 行 → 专门针对无障碍类 Bug,直接暴露相关代码

这样 Round 1 用 20 个文件 × 500 token ≈ 10,000 token 就能完成初筛,而不需要发送 20 个完整文件(可能要 200,000+ token)。

Token 优化策略

这里的漏斗设计是整个工具的核心性能优化:

1
2
3
4
5
Step 2: 20个候选文件(纯本地,0 token)

Round 1: 20个文件的摘要(~500 token/文件 = ~5000 token)→ 筛选到 5 个

Round 2: 5个文件的完整内容(每个独立调用)

如果直接对 20 个文件都发送完整内容,token 消耗将极其巨大(一个 ObjC 文件可能有数千行)。

第二轮:逐文件精确定位(LLM 调用 #3 ~ #7)

对筛选出的 Top 5(MAX_PRECISE_FILES = 5)文件,逐个调用 locateInFile()

大文件智能截取:对超过 500 行的文件(ObjC 文件通常非常长),不是简单截断前 500 行,而是使用 smartExtract() 进行智能截取:

  1. 保留头部 50 行(imports、类声明)
  2. 从 bug 描述中提取搜索关键词extractKeywordsFromDescription()
    • 提取英文标识符:accessibility, button, more, navigation
    • 提取中文关键词:导航栏, 更多, 按钮, 无障碍
  3. 搜索关键词在文件中的出现位置,取前后各 15 行上下文
  4. 合并重叠区间,避免重复
  5. 如果关键词匹配不到,回退为均匀采样关键声明行

最终生成带行号的截取内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1: #import "QMPersonalInfoViewController.h"
2: ...
...
50: ...

... (skipped to line 5660) ...

5660: // 导航栏更多按钮
5661: ...
5667: UIButton *button = [ComHelper createCustomButtonByImageName:@"personal_info_header_more"
...
5673: button.accessibilityLabel = QMLocalizedString(@"SVCC_SHOW_MORE", nil);

... (total 6000 lines, showing 350 relevant lines)

构建 Prompt

1
2
3
4
5
6
7
你是 iOS 开发专家。请在以下代码中精确定位 bug 所在位置。

Bug 描述:个人主页导航栏更多按钮无障碍响应错误

文件:/path/to/QMPersonTitleView.m
```code
[带行号的文件内容/智能截取内容]
1
2
3
4
5
6
7
请返回 JSON:
{
"lineStart": 问题代码起始行号,
"lineEnd": 问题代码结束行号,
"confidence": 0到1之间的置信度数值,
"explanation": "定位原因的详细说明"
}

5 个文件的 LLM 返回结果

文件 行号 置信度 核心发现
QMPersonTitleView.m 189-195 90% accessibilityLabel 被设置后又被硬编码为 @"更多" 覆盖
QMPersonHeaderCell.m 70-70 90% accessibilityLabel = moreBtnTitle 但缺少完整的无障碍配置
QMPersonalInfoViewController.m 5667-5673 85% 导航栏更多按钮创建处,可能存在本地化字符串问题
ProfileViewController_V3Pad.m 1010-1013 85% accessibilityLabel:atIndex: 方法始终返回空字符串 @""
ProfileViewController_V3+Follow.m 176-200 85% 关注按钮点击处理缺少无障碍属性更新

结果排序

所有定位结果按 confidence(置信度)降序排序:

1
return results.sort((a, b) => b.confidence - a.confidence);

90% 的两个结果排在前面,85% 的三个排在后面。

提取代码片段

对每个定位结果,根据 lineStartlineEnd 从完整文件内容中截取代码:

1
2
const contentLines = content.split("\n");
const codeSnippet = contentLines.slice(lineStart - 1, lineEnd).join("\n");

结果保存

定位结果同时输出到终端和 JSON 文件:

1
2
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const resultFile = path.join(RESULTS_DIR, `result-${timestamp}.json`);
1
Results saved to: .../result-2026-03-06T14-04-42-624Z.json

API 调用汇总

本次 locate 命令总共进行了 7 次 LLM API 调用

次序 阶段 输入 输出 预估 Token
1 Step 1: 信息提取 bug 描述 + prompt模板 BugInfo JSON ~500
2 Step 3 Round 1: 摘要筛选 10个文件摘要 5个相关文件路径 ~6000
3-7 Step 3 Round 2: 精确定位 每个文件的内容(智能截取) 行号 + 置信度 + 解释 ~3000-8000/次

Step 2 完全在本地执行(ripgrep + SQLite + find + git),无 API 调用,0 token 消耗。


关键设计决策总结

多策略并行 + 分数融合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
graph TD
A[Bug 描述] --> B[Step 1: LLM 提取 BugInfo]
B --> C1[策略1: 直接路径]
B --> C2[策略2: ripgrep 搜索]
B --> C3[策略3: 索引查询+页面映射]
B --> C4[策略4: 目录推断]
B --> C5[策略5: Git 热点]
B --> C6[策略6: 类型专项]
C1 --> D[分数合并 + 交叉验证加分]
C2 --> D
C3 --> D
C4 --> D
C5 --> D
C6 --> D
D --> E[Top 20 候选文件]
E --> F[Round 1: 摘要筛选 → Top 5]
F --> G[Round 2: 逐文件精确定位]
G --> H[按置信度排序输出]

分数体系设计

来源 分值 设计意图
直接路径 100 代码扫描报告给出的路径几乎必中
页面映射 40 人工维护的映射最可靠
索引类名匹配 30 FTS5 匹配到类名,可信度高
Bug 类型专项 15 有针对性的搜索
索引关键词匹配 8 关键词范围更广,可能有噪声
目录推断 8 目录名和模块名可能不完全对应
ripgrep 6 全文搜索覆盖广但噪声多
Git 热点 2 纯统计信息,低权重兜底
交叉验证加分 +5/策略 多策略命中说明文件高度相关

Token 优化漏斗

1
2
3
4
5
6
7
17,522 源文件
↓ 本地 6 策略并行筛选(0 token)
20 候选文件
↓ 读取 Top 10 文件摘要(~500 token/文件 × 10)
10 → 5 文件(Round 1 筛选,~6000 token)
↓ 逐文件精确定位,大文件智能截取
5 个定位结果(Round 2,~5000 token/文件 × 5)

总 token 消耗约: 30,000-40,000 token,相比直接将 20 个大文件发给 AI(可能 500,000+ token),节省了 90% 以上

大文件智能截取 vs 简单截断

简单截断前 500 行的问题:ObjC 文件头部通常是 #import 和属性声明,真正有 bug 的代码可能在第 5000+ 行。智能截取通过关键词搜索 + 上下文窗口(前后各 15 行)确保问题代码被覆盖。

本次案例中 QMPersonalInfoViewController.m 的问题代码在第 5667 行,如果简单截断前 500 行将完全漏掉。


本次定位效果评价

对于 bug 描述 **”个人主页导航栏更多按钮无障碍响应错误”**:

  1. Step 1 准确识别为 accessibility 类型,正确推断了 个人主页 页面名,关键词覆盖了 MoreButton/MoreBtn/accessibilityLabel/accessibilityTraits 等关键变体
  2. Step 2 的 Top 1 就是主文件 QMPersonalInfoViewController.m(81 分),得益于页面映射(40分)+ ripgrep 多关键词命中(36分)+ 交叉验证加分(5分)
  3. Step 3 最终输出了 5 个定位结果,最高置信度 90% 的两个结果精确指向了 accessibilityLabel 被错误覆盖和不完整设置的代码行
昨天以前微言 | wyanassert

VSCode 插件全部无法激活?一次从日志到根源的排查记录

作者 wyanassert
2026年3月3日 16:34

VSCode 插件全部无法激活?一次从日志到根源的排查记录

引言

某天,你像往常一样打开 Visual Studio Code,却发现所有已安装的插件都失效了——代码补全没了、Git 信息不见了、主题也变回了默认。更诡异的是,重装软件、清理缓存、升级版本……常规手段统统无效。插件市场明明显示已安装,但就是无法激活。这究竟是怎么回事?

最近我就遇到了这样的棘手问题,经过一番抽丝剥茧,终于揪出了幕后黑手——一个看似无害的 GitBlame 扩展。下面我将完整还原整个排查过程,希望能为遇到类似问题的朋友提供一份实用的“避坑指南”。


第一阶段:基础排查(全军覆没)

当所有插件都无法激活时,首先排除环境因素:

  • 检查 VSCode 位置:确保 Visual Studio Code.app 位于“应用程序”文件夹,而非“下载”或桌面(权限问题会导致插件加载失败)。
  • 清理缓存与配置文件
    1
    2
    3
    rm -rf ~/.vscode
    rm -rf ~/Library/Application\ Support/Code
    rm -rf ~/Library/Caches/com.microsoft.VSCode
  • 彻底重装:删除上述所有文件后,从官网下载最新版重装。

然而,这一套组合拳下来,问题依旧。看来不是简单的缓存损坏。


第二阶段:启用“侦探模式”——挖掘日志

常规手段无效,就需要让 VSCode 自己“开口说话”。打开 帮助切换开发人员工具(或 Cmd+Option+I),在 控制台(Console)输出(Output) 面板中寻找线索。

果然,一条醒目的红色错误映入眼帘:

1
2
ERR Extension 'TME.continuecode CANNOT USE these API proposals 'extensionRuntime'. 
You MUST start in extension development mode or use the --enable-proposed-api command line flag

这里出现了一个陌生的扩展 TME.continuecode,它试图使用 提案 API(Proposed API)——这是 VSCode 内部开发中的接口,普通扩展无权调用。这种错误可能导致扩展半激活,甚至阻塞整个扩展宿主进程。

同时,日志中还发现了两个 CMake 扩展的冲突警告:

1
WARN [twxs.cmake]: 无法注册“cmake.cmakePath”。此属性已注册。

多个扩展争夺同一配置项,虽不致命,但加剧了环境的不稳定性。

初步行动:移除问题扩展

1
2
rm -rf ~/.vscode/extensions/tme.continuecode-*
rm -rf ~/.vscode/extensions/twxs.cmake-*

重启 VSCode 后,TME.continuecode 的错误消失了,但……扩展宿主依然无响应!日志中只剩下:

1
INFO Extension host (LocalProcess pid: 12485) is unresponsive.

看来凶手不止一个。


第三阶段:终极排查法——禁用所有扩展,逐个启用

当错误日志无法直接定位时,就要用最原始也最有效的方法:控制变量法

1. 以禁用所有扩展的模式启动

1
code --disable-extensions

启动后,VSCode 响应迅速,所有内置功能正常。这证实问题 100% 出在某个第三方扩展上

2. 二分法逐个启用扩展

退出纯净模式,正常打开 VSCode(此时所有扩展仍处于禁用状态)。然后进入扩展面板,每次启用一个扩展,重启观察是否复现无响应。这个过程需要耐心,但能精确锁定目标。

经过几轮测试,当启用 GitBlame 后,扩展宿主再次卡死。卸载该扩展,一切恢复如初。


第四阶段:真相大白

GitBlame 是一个提供 Git 逐行注释(blame)信息的扩展,功能简单但实用。但是十小时前这个插件更新后, 导致了问题.

替代方案

  • GitLens:功能强大且持续维护的 Git 工具,不仅能显示 blame,还提供丰富的仓库浏览功能。
  • Git History:轻量级替代品,专注于文件历史和逐行注释。

安装 GitLens 后,一切功能正常,再无卡顿。


总结:排查思路回顾

  1. 基础检查:确保 VSCode 安装位置正确,清理缓存。
  2. 日志分析:打开开发者工具,查看控制台和“扩展宿主”输出,寻找显式错误。
  3. 处理明显错误:如提案 API 滥用、扩展冲突,先移除可疑扩展。
  4. 禁用所有扩展:用 code --disable-extensions 确认问题是否由扩展引起。
  5. 二分法逐个启用:定位具体肇事扩展。
  6. 寻找替代或更新:对于老旧扩展,果断换用维护活跃的同类工具。

一些有用的命令

用途 命令
以最大日志级别启动 code --log trace --verbose
禁用所有扩展 code --disable-extensions
使用临时用户数据目录 code --user-data-dir ~/Desktop/vscode-temp
删除指定扩展 rm -rf ~/.vscode/extensions/扩展名-*

心得

  • 日志是第一生产力:遇到诡异问题,先看日志,往往能直接定位。
  • 老旧扩展是定时炸弹:长期未更新的扩展可能与新版 VSCode 不兼容,尽量选用维护活跃的替代品。
  • 控制变量法永不过时:当错误信息模糊时,通过排除法缩小范围是最可靠的手段。

希望这次分享能帮助你快速解决类似的插件故障。如果你也有过奇葩的排查经历,欢迎留言交流!

❌
❌