Skip to content

CodeBuddy IDE 误报「已接入」:只读探测会凭空创建 %APPDATA%\CodeBuddy CN 目录 #91

Description

@misaka20002

环境

  • workbuddy-switch 0.1.46(Windows x64,桌面版;同一 core 也用于 npm / webui 版)
  • CodeBuddy IDE(国内版)客户端已卸载,本机无其进程、无可执行文件
  • 与 WorkBuddy 账号数量、登录状态无关,纯净环境亦可复现

现象

  1. 账号页产品状态图标 hover 的 tooltip 显示:

    CodeBuddy IDE:已接入 · 当前账号:未检测到
    

    即 installed = true, running = false,但"当前账号"取不到(无状态文件)。

  2. 反查本机:无 CodeBuddy CN.exe、无相关进程、无 exe 路径缓存,也不存在任何 state.vscdb。

  3. 手动删除 %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)

于是:

  1. create_dir_all 建出 %APPDATA%\CodeBuddy CN\User\globalStorage(三层空目录,不建文件);

  2. 函数返回首选路径,read_secret_for 的 !db_path.exists() 判定为真 → Ok(None),探测报"未找到 secret";

  3. 随后 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,不会自建目录。

复现步骤

  1. 确保本机没有 CodeBuddy IDE 客户端(或至少其 state.vscdb 不存在);
  2. 删除 %APPDATA%\CodeBuddy CN,确认删除后目录确实不存在;
  3. 启动 workbuddy-switch,停留在账号页;
  4. 观察到:
    • %APPDATA%\CodeBuddy CN\User\globalStorage 被重建(空目录,无文件);
    • 产品状态图标 tooltip 显示「CodeBuddy IDE:已接入 · 当前账号:未检测到」;
    • 账号卡片上的 CodeBuddy IDE 切换按钮为可点击状态,点击后报错。

期望行为

  • 只读探测 / 状态查询不应创建任何目录或文件;
  • installed 不应由"用户数据目录存在"推导。若希望表达"有残留数据",建议单列字段——status() 已经返回 dataDir / dbExists,UI 层可据此区分,不必复用 installed。

建议修法

  1. 读路径去掉建目录副作用。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,不能用于只读探测。

  2. 收敛 installed 语义为"存在可运行的客户端"(app_path.is_some());需要"装过但客户端已移除"的中间态时另开字段。

  3. 补回归测试:在临时目录下传入不存在的 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 逐字存在,修的时候建议一并处理。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions