环境
- workbuddy-switch
0.1.46(Windows x64,桌面版;同一 core 也用于 npm / webui 版)
- CodeBuddy IDE(国内版)客户端已卸载,本机无其进程、无可执行文件
- 与 WorkBuddy 账号数量、登录状态无关,纯净环境亦可复现
现象
-
账号页产品状态图标 hover 的 tooltip 显示:
CodeBuddy IDE:已接入 · 当前账号:未检测到
即 installed = true, running = false,但"当前账号"取不到(无状态文件)。
-
反查本机:无 CodeBuddy CN.exe、无相关进程、无 exe 路径缓存,也不存在任何 state.vscdb。
-
手动删除 %APPDATA%\CodeBuddy CN 后重启 App,该目录会再次出现,且只包含空目录结构、不产生任何文件:
%APPDATA%\CodeBuddy CN\User\globalStorage
根因
启动时账号页会做一次"探测本机登录态",这条只读路径里带了建目录的副作用,而 installed 又恰好依赖该目录是否存在,于是形成自我维持的状态误报。
调用链(0.1.46):
| 步骤 |
位置 |
| 启动 → 账号页挂载 |
src/pages/AccountsPage.tsx:325 useEffect;国内版走 :335 |
| Tauri 命令 |
src-tauri/src/commands.rs:153 |
| 探测当前登录 |
crates/wb-switch-core/src/modules/codebuddy_cn_ide.rs:1257 detect_current_account() |
| 读 secret |
同上 :1258 → vscode_cn_inject.rs:621 → read_secret_for:634 |
| 解析 db 路径(副作用发生地) |
vscode_cn_inject.rs:220 resolve_state_db_path_for() → :237-240 |
| 发现 db 不存在,返回"未找到" |
vscode_cn_inject.rs:639 if !db_path.exists() { return Ok(None) } |
| 紧接着查状态 |
AccountsPage.tsx:347 → codebuddy_cn_ide.rs:1165 status() |
关键代码 crates/wb-switch-core/src/modules/vscode_cn_inject.rs:229-241:
let candidates = [
root.join("User").join("globalStorage").join("state.vscdb"),
root.join("globalStorage").join("state.vscdb"),
root.join("state.vscdb"),
];
if let Some(path) = candidates.iter().find(|p| p.exists()) {
return Ok(path.clone());
}
let preferred = candidates[0].clone();
if let Some(parent) = preferred.parent() {
std::fs::create_dir_all(parent)?; // ← 只读探测在这里留下了持久副作用
}
Ok(preferred)
于是:
-
create_dir_all 建出 %APPDATA%\CodeBuddy CN\User\globalStorage(三层空目录,不建文件);
-
函数返回首选路径,read_secret_for 的 !db_path.exists() 判定为真 → Ok(None),探测报"未找到 secret";
-
随后 status() 计算 installed:
// codebuddy_cn_ide.rs:1168-1169
let installed =
codebuddy_cn_app_path().is_some() || data_dir.as_ref().map(|p| p.exists()).unwrap_or(false);
第二项刚被第 1 步造出来 → installed = true → tooltip 显示「已接入」。
resolve_state_db_path_for 的语义是"解析 db 路径,必要时准备目录",用在写入路径(inject_secret_for:735)是合理的;问题在于读路径复用了它。
影响
- 状态误报且自我维持:客户端未安装的机器显示「已接入」,用户每次删除目录都会被下次探测重建,无法通过手动清理摆脱。
- 污染用户目录:凭空在用户数据目录下创建空壳目录。
- 误启用交互:切换按钮的可用性直接取自
installed(AccountsPage.tsx:1113 → components/account-card.tsx:581 disabled={... || !codebuddyCnIdeAvailable || ...}),按钮被误启用,点击必然在注入 / 启动阶段失败。
- 国际版同样受影响:
codebuddy_ide.rs:41 走同一条 read_secret_for,只是目录换成 %APPDATA%\CodeBuddy。
- VS Code 扩展不受影响:
vscode_ext.rs:416 用的是只读的 has_secret_row_for,不会自建目录。
复现步骤
- 确保本机没有 CodeBuddy IDE 客户端(或至少其
state.vscdb 不存在);
- 删除
%APPDATA%\CodeBuddy CN,确认删除后目录确实不存在;
- 启动 workbuddy-switch,停留在账号页;
- 观察到:
%APPDATA%\CodeBuddy CN\User\globalStorage 被重建(空目录,无文件);
- 产品状态图标 tooltip 显示「CodeBuddy IDE:已接入 · 当前账号:未检测到」;
- 账号卡片上的 CodeBuddy IDE 切换按钮为可点击状态,点击后报错。
期望行为
- 只读探测 / 状态查询不应创建任何目录或文件;
installed 不应由"用户数据目录存在"推导。若希望表达"有残留数据",建议单列字段——status() 已经返回 dataDir / dbExists,UI 层可据此区分,不必复用 installed。
建议修法
-
读路径去掉建目录副作用。read_secret_for:638 先把路径解析出来再判断存在性,而 :639 的早返回说明它本来就不需要"准备目录"。可改为只挑已存在的候选(与 has_secret_row_for:680-687 同一段逻辑),或先复用只读的 has_secret_row_for 做存在性判断。该函数注释里已经写明了这一点:
与 resolve_state_db_path_for 的关键差异:不创建任何目录或文件…… resolve_state_db_path_for 会 create_dir_all,不能用于只读探测。
-
收敛 installed 语义为"存在可运行的客户端"(app_path.is_some());需要"装过但客户端已移除"的中间态时另开字段。
-
补回归测试:在临时目录下传入不存在的 root,断言 read_secret_for / detect_current_account 执行后 root 与其 User/globalStorage 均未被创建。has_secret_row_for 已有类似的只读断言可参考(vscode_cn_inject.rs:913-917)。
备注
- 该误报与
running 无关:running 单独走进程检测,本场景下为 false,所以显示的是「已接入」而不是「运行中」。
- 同源问题(
installed 由 data dir 推导)在国际版 codebuddy_ide.rs:984-985 逐字存在,修的时候建议一并处理。
环境
0.1.46(Windows x64,桌面版;同一 core 也用于 npm / webui 版)现象
账号页产品状态图标 hover 的 tooltip 显示:
即
installed = true, running = false,但"当前账号"取不到(无状态文件)。反查本机:无
CodeBuddy CN.exe、无相关进程、无 exe 路径缓存,也不存在任何state.vscdb。手动删除
%APPDATA%\CodeBuddy CN后重启 App,该目录会再次出现,且只包含空目录结构、不产生任何文件:根因
启动时账号页会做一次"探测本机登录态",这条只读路径里带了建目录的副作用,而
installed又恰好依赖该目录是否存在,于是形成自我维持的状态误报。调用链(
0.1.46):src/pages/AccountsPage.tsx:325useEffect;国内版走:335src-tauri/src/commands.rs:153crates/wb-switch-core/src/modules/codebuddy_cn_ide.rs:1257detect_current_account():1258→vscode_cn_inject.rs:621→read_secret_for:634vscode_cn_inject.rs:220resolve_state_db_path_for()→:237-240vscode_cn_inject.rs:639if !db_path.exists() { return Ok(None) }AccountsPage.tsx:347→codebuddy_cn_ide.rs:1165status()关键代码
crates/wb-switch-core/src/modules/vscode_cn_inject.rs:229-241:于是:
create_dir_all建出%APPDATA%\CodeBuddy CN\User\globalStorage(三层空目录,不建文件);函数返回首选路径,
read_secret_for的!db_path.exists()判定为真 →Ok(None),探测报"未找到 secret";随后
status()计算installed:第二项刚被第 1 步造出来 →
installed = true→ tooltip 显示「已接入」。resolve_state_db_path_for的语义是"解析 db 路径,必要时准备目录",用在写入路径(inject_secret_for:735)是合理的;问题在于读路径复用了它。影响
installed(AccountsPage.tsx:1113→components/account-card.tsx:581disabled={... || !codebuddyCnIdeAvailable || ...}),按钮被误启用,点击必然在注入 / 启动阶段失败。codebuddy_ide.rs:41走同一条read_secret_for,只是目录换成%APPDATA%\CodeBuddy。vscode_ext.rs:416用的是只读的has_secret_row_for,不会自建目录。复现步骤
state.vscdb不存在);%APPDATA%\CodeBuddy CN,确认删除后目录确实不存在;%APPDATA%\CodeBuddy CN\User\globalStorage被重建(空目录,无文件);期望行为
installed不应由"用户数据目录存在"推导。若希望表达"有残留数据",建议单列字段——status()已经返回dataDir/dbExists,UI 层可据此区分,不必复用installed。建议修法
读路径去掉建目录副作用。
read_secret_for:638先把路径解析出来再判断存在性,而:639的早返回说明它本来就不需要"准备目录"。可改为只挑已存在的候选(与has_secret_row_for:680-687同一段逻辑),或先复用只读的has_secret_row_for做存在性判断。该函数注释里已经写明了这一点:收敛
installed语义为"存在可运行的客户端"(app_path.is_some());需要"装过但客户端已移除"的中间态时另开字段。补回归测试:在临时目录下传入不存在的 root,断言
read_secret_for/detect_current_account执行后 root 与其User/globalStorage均未被创建。has_secret_row_for已有类似的只读断言可参考(vscode_cn_inject.rs:913-917)。备注
running无关:running单独走进程检测,本场景下为false,所以显示的是「已接入」而不是「运行中」。installed由 data dir 推导)在国际版codebuddy_ide.rs:984-985逐字存在,修的时候建议一并处理。