Skip to content

fix: Expand Windows ~\\ home paths and hide phantom (session) entries in the desktop session list#5253

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
mvanhorn:fix/5244-windows-tilde-expand-phantom-sessions
Jun 18, 2026
Merged

fix: Expand Windows ~\\ home paths and hide phantom (session) entries in the desktop session list#5253
wenshao merged 1 commit into
QwenLM:mainfrom
mvanhorn:fix/5244-windows-tilde-expand-phantom-sessions

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What this PR does

This PR fixes two related defects that surface on the Windows desktop client after running certain skill/tool tasks: phantom conversations titled (session) showing up in the session list, and a literal ~ folder being created under the app install directory.

  1. Windows ~\ home-path expansion (packages/desktop/packages/shared/src/utils/paths.ts): expandPath() previously only expanded the Unix-style ~/ prefix. A Windows-style ~\Documents\... or ~\.craft-agent\... path fell through to the relative-path branch and was resolved against the process working directory — which on the desktop app is the install directory — creating a stray literal ~ folder. The fix extends the tilde branch to also match ~\, splitting the remainder on either separator (/ or \) and joining each segment against the home directory, so Windows-style home paths expand correctly. This mirrors the dual-separator handling already used elsewhere in the codebase.

  2. Hide empty (session) placeholder mirrors in the desktop session list (packages/desktop/packages/server-core/src/sessions/SessionManager.ts): The phantom (session) entries are unresolved Qwen canonical-mirror placeholders with zero messages and no preview/content. The previous isUnresolvedQwenCanonicalMirror() logic short-circuited to "show this session" whenever any of its createdAt / lastUsedAt / lastMessageAt timestamps was non-zero — which these placeholders always have — so they were never filtered out. The fix:

    • removes that timestamp short-circuit so a non-zero timestamp no longer forces an empty placeholder to be shown;
    • routes the empty-content check through hasNoRenderableLocalMessages(), which considers both the loaded message array and the persisted messageCount;
    • hardens the helpers against malformed records by null-safely reading managed.messages?.length ?? 0 and by only treating managed.name as a title when it is actually a string.

    Net effect: empty (session) placeholders are filtered out of the session list, while sessions with a real title or real message content continue to be shown.

Why it's needed

On the Windows desktop client (Desktop v0.04), running skill/tool tasks produced extra (session) conversations the user never created — they open with no real chat content, only tool-call/execution records or nearly empty — and also caused a literal ~ directory to be created under the app install directory. Per issue #5244, the expected behavior is: ~\... should expand to the user home just like ~/..., no literal ~ folder should be created under the install directory, and empty external sessions (zero messageCount, no preview/prompt/title) should not appear as user-visible conversations in the main session list.

Reviewer Test Plan

How to verify

  • Windows path expansion — call expandPath('~\\.craft-agent\\foo') and confirm it returns join(homedir(), '.craft-agent', 'foo') rather than a path under the current working directory; confirm expandPath('~\\') resolves to the home directory; confirm the existing ~, ~/, $HOME, ${HOME} cases are unchanged. These are covered by the new unit tests in packages/desktop/packages/shared/src/utils/__tests__/paths.test.ts.
  • Phantom session filtering — construct a (session) managed session with zero messages and non-zero timestamps and confirm getSessions() no longer returns it; confirm sessions with a real title or real message content are still returned; confirm isUnresolvedQwenCanonicalMirror() does not throw on a malformed placeholder record. These are covered by the new unit tests in packages/desktop/packages/server-core/src/sessions/SessionManager.test.ts.
  • Standard workspace gates apply: lint, typecheck, and the full test suite (run via the repo's npm workspace scripts).

Evidence (Before & After)

N/A — non-visible logic/path-handling fixes; behavior is covered by the new unit tests above. The user-facing symptom (phantom (session) rows in the desktop session list) is documented with screenshots in issue #5244; this change removes those rows by filtering empty placeholder mirrors.

Tested on

OS Status
🍏 macOS ✅ CI
🪟 Windows ✅ CI
🐧 Linux ✅ CI

✅ tested · ⚠️ not tested · N/A

Environment (optional)

Unit tests added for both fixes; validated through the repository CI matrix (Node 22.x on ubuntu/macos/windows-latest).

Risk & Scope

  • Main risk or tradeoff: Low; scoped to the desktop shared expandPath() helper and the desktop SessionManager placeholder-filtering logic. The path change only adds a new ~\ branch and broadens segment splitting; the session change only stops showing empty (session) placeholders that have a timestamp.
  • Not validated / out of scope: No unrelated refactors, no public API changes, no UI redesigns; non-Windows path behavior is unchanged.
  • Breaking changes / migration notes: None.

Linked Issues

Fixes #5244

中文说明

这个 PR 做了什么

本 PR 修复了在 Windows 桌面端运行某些 skill / tool 任务后出现的两个相关缺陷:会话列表中出现名为 (session) 的幽灵会话,以及在应用安装目录下创建出一个字面量 ~ 文件夹。

  1. Windows ~\ home 路径展开packages/desktop/packages/shared/src/utils/paths.ts):expandPath() 之前只展开 Unix 风格的 ~/ 前缀。Windows 风格的 ~\Documents\...~\.craft-agent\... 路径不会被命中,落到相对路径分支,被解析到进程当前工作目录下——在桌面应用里就是安装目录——从而创建出一个多余的字面量 ~ 文件夹。修复方式是把波浪号分支扩展为同时匹配 ~\,把剩余部分按任意分隔符(/\)切分后逐段与 home 目录拼接,使 Windows 风格的 home 路径能正确展开。这与代码库其他位置已有的双分隔符处理保持一致。

  2. 在桌面会话列表中隐藏空的 (session) 占位镜像packages/desktop/packages/server-core/src/sessions/SessionManager.ts):这些幽灵 (session) 条目是未解析的 Qwen canonical-mirror 占位记录,消息数为零、没有预览/内容。之前的 isUnresolvedQwenCanonicalMirror() 逻辑只要 createdAt / lastUsedAt / lastMessageAt 中任意一个时间戳非零,就会短路成"显示该会话"——而这些占位记录总是带有时间戳——所以它们永远不会被过滤掉。本次修复:

    • 移除该时间戳短路,使非零时间戳不再强制显示空占位记录;
    • 把空内容判断改为走 hasNoRenderableLocalMessages(),它会同时考虑已加载的消息数组和持久化的 messageCount
    • 通过空安全地读取 managed.messages?.length ?? 0、并且只在 managed.name 确实是字符串时才将其当作标题,从而增强对畸形记录的健壮性。

    最终效果:空的 (session) 占位记录被从会话列表中过滤掉,而拥有真实标题或真实消息内容的会话继续正常显示。

为什么需要

在 Windows 桌面端(Desktop v0.04)上,运行 skill / tool 任务会产生用户从未主动创建的额外 (session) 会话——打开后没有真实聊天内容,只有工具调用 / 执行记录,或几乎为空——同时还会在应用安装目录下创建出一个字面量 ~ 目录。根据 issue #5244,预期行为是:~\... 应当像 ~/... 一样展开到用户 home;安装目录下不应创建字面量 ~ 文件夹;messageCount 为零且没有 preview / prompt / title 的外部 session 不应出现在主会话列表中作为用户可见的普通对话。

审阅者测试计划

如何验证

  • Windows 路径展开——调用 expandPath('~\\.craft-agent\\foo'),确认它返回 join(homedir(), '.craft-agent', 'foo'),而不是当前工作目录下的路径;确认 expandPath('~\\') 解析到 home 目录;确认已有的 ~~/$HOME${HOME} 用例行为不变。这些由 packages/desktop/packages/shared/src/utils/__tests__/paths.test.ts 中新增的单元测试覆盖。
  • 幽灵会话过滤——构造一个零消息、带非零时间戳的 (session) 受管会话,确认 getSessions() 不再返回它;确认拥有真实标题或真实消息内容的会话仍被返回;确认 isUnresolvedQwenCanonicalMirror() 在畸形占位记录上不抛异常。这些由 packages/desktop/packages/server-core/src/sessions/SessionManager.test.ts 中新增的单元测试覆盖。
  • 标准工作区检查同样适用:lint、类型检查,以及完整测试套件(通过仓库的 npm workspace 脚本运行)。

证据(前后对比)

N/A——这是不可见的逻辑 / 路径处理修复,行为由上面新增的单元测试覆盖。用户可见症状(桌面会话列表中的幽灵 (session) 行)在 issue #5244 中已有截图记录;本次改动通过过滤空占位镜像移除了这些行。

测试平台

操作系统 状态
🍏 macOS ✅ CI
🪟 Windows ✅ CI
🐧 Linux ✅ CI

✅ 已测试 · ⚠️ 未测试 · N/A

运行环境(可选)

为两处修复均新增了单元测试;通过仓库 CI 矩阵验证(Node 22.x,ubuntu / macos / windows-latest)。

风险与范围

  • 主要风险或权衡:低;范围限定在桌面 shared 的 expandPath() 辅助函数和桌面 SessionManager 的占位过滤逻辑。路径改动只新增 ~\ 分支并扩展分段切分;会话改动只停止显示带时间戳的空 (session) 占位记录。
  • 未验证 / 范围外:无无关重构、无公共 API 变更、无 UI 重设计;非 Windows 路径行为保持不变。
  • 破坏性变更 / 迁移说明:无。

关联 Issue

Fixes #5244

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, @mvanhorn! The fix itself looks targeted and addresses a real issue (#5244).

However, the PR body doesn't follow our pull request template. The current body uses ## Summary and ## Changes headings, but the template requires:

  • ## What this PR does — describe the change in prose
  • ## Why it's needed — motivation and user-facing benefit
  • ## Reviewer Test Plan — this is the big one. It needs:
    • How to verify — steps a reviewer can follow to confirm the fix
    • Evidence (Before & After) — screenshots, logs, or tmux output showing the bug and the fix (especially important for a Windows-specific issue)
    • Tested on — which OSes you verified on (macOS / Windows / Linux)
  • ## Risk & Scope — main risk, what's out of scope, breaking changes
  • ## Linked IssuesFixes #5244 (you have this, just needs the heading)

Could you restructure the PR body to follow the template? The Reviewer Test Plan section is especially important — without it, reviewers can't easily verify the Windows-specific path expansion fix or the phantom session filtering change.

中文说明

感谢贡献,@mvanhorn!修复本身看起来有针对性,解决了真实问题(#5244)。

但 PR 正文没有按照我们的 PR 模板 填写。当前使用的是 ## Summary## Changes,而模板要求:

  • ## What this PR does — 用文字描述改动
  • ## Why it's needed — 动机和用户可见的收益
  • ## Reviewer Test Plan — 这是最重要的部分,需要包含:
    • How to verify — 审查者如何验证修复
    • Evidence (Before & After) — 截图、日志或 tmux 输出,展示修复前后的对比(对 Windows 特定问题尤为重要)
    • Tested on — 你在哪些操作系统上验证过
  • ## Risk & Scope — 主要风险、不在范围内的内容、破坏性变更
  • ## Linked IssuesFixes #5244(已有,需要放在对应标题下)

请按照模板重新组织 PR 正文。Reviewer Test Plan 部分尤其重要——没有它,审查者很难验证 Windows 路径展开修复和幽灵会话过滤的改动。

Qwen Code · qwen3.7-max

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found. The two fixes are targeted and correct — expandPath now expands ~\ using the same dual-separator split(/[/\\]+/) pattern already used in storage.ts/qwen-agent.ts/vscode paths.ts, and isUnresolvedQwenCanonicalMirror correctly drops the timestamp escape-hatch (timestamps were a false "keep" signal present on every mirror) while the awaited refreshExternalSessions in GET_SESSIONS still resolves content-bearing sessions before filtering. New tests pass and both packages typecheck. LGTM! ✅ — claude-opus-4-8[1m] via Qwen Code /qreview

@wenshao

wenshao commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @mvanhorn!

Template looks good ✓ — the prior CHANGES_REQUESTED review flagged template headings, but the body has since been updated to follow the template correctly.

On direction: clearly aligned. Two real Windows desktop bugs — phantom (session) entries and a stray ~ folder under the install directory — both reported in #5244. These are the kind of polish fixes that matter for the desktop experience. No direction concerns.

On approach: the scope feels right. The expandPath() fix is a one-line branch extension (~\ alongside ~/) with dual-separator splitting that mirrors patterns already used in storage.ts/qwen-agent.ts/vscode paths.ts. The session filtering fix correctly removes the timestamp short-circuit (every mirror has timestamps, so it was a false "keep" signal) and delegates to the existing hasNoRenderableLocalMessages() helper. Two fixes, four files, +172/-12 — focused and minimal.

Moving on to code review. 🔍

中文说明

感谢贡献,@mvanhorn

模板完整 ✓ —— 之前的 CHANGES_REQUESTED 审查指出了模板标题问题,但正文已按模板正确更新。

方向:完全对齐。两个真实的 Windows 桌面端 bug——幽灵 (session) 条目和安装目录下多余的 ~ 文件夹——均在 #5244 中有报告。这类打磨修复对桌面端体验很重要,无方向性顾虑。

方案:范围合理。expandPath() 修复是在 ~/ 旁边新增 ~\ 分支,配合双分隔符切分,与 storage.ts/qwen-agent.ts/vscode paths.ts 中已有的模式一致。会话过滤修复正确移除了时间戳短路(每个镜像都有时间戳,因此它是错误的"保留"信号),并委托给已有的 hasNoRenderableLocalMessages() 辅助函数。两处修复、四个文件、+172/-12——聚焦且最小化。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Reviewed the diff against my independent proposal. The PR matches what I'd do:

Path expansion — the ~\ branch with .split(/[/\\]+/) mirrors the dual-separator handling already in storage.ts/qwen-agent.ts. Null-safe, handles ~\, ~\foo\bar, ~\\ (bare), and preserves existing ~/, ~, $HOME, ${HOME} behavior. Clean.

Session filtering — the core insight is correct: the timestamp short-circuit was a false "keep" signal since every mirror has timestamps. Removing it and routing through hasNoRenderableLocalMessages() is the right fix. The null-safety hardening (managed.messages?.length ?? 0, typeof managed.name === 'string') is good defensive coding for malformed records.

No correctness bugs, no security concerns, no AGENTS.md violations. The diff is focused — 4 files, +172/-12, no drive-by refactors.

Test Results

New tests (PR-added) — all 7 pass ✅

$ bun test packages/shared/src/utils/__tests__/paths.test.ts
bun test v1.3.14 (0d9b296a)
✓ expandPath > expands Windows-style tilde paths under home [1.11ms]
✓ expandPath > expands bare Windows-style tilde prefixes [0.08ms]
✓ expandPath > keeps existing home expansion behavior [0.10ms]
✓ expandPath > keeps absolute and relative path behavior unchanged [0.10ms]
 4 pass · 0 fail · 8 expect() calls

$ bun test packages/server-core/src/sessions/SessionManager.test.ts
bun test v1.3.14 (0d9b296a)
✓ SessionManager Qwen canonical mirror filtering > hides empty placeholder mirrors even when they have timestamps [1.33ms]
✓ SessionManager Qwen canonical mirror filtering > keeps external sessions with a real title or content [0.58ms]
✓ SessionManager Qwen canonical mirror filtering > treats malformed empty placeholder records as filterable [0.15ms]
 3 pass · 0 fail · 4 expect() calls

Regression check — base branch confirms tests catch the bugs

Stashing the PR fix and re-running the new tests:

# Base branch (no fix), paths.test.ts:
 2 pass · 2 fail   ← Windows-style tests fail as expected

# Base branch (no fix), SessionManager.test.ts:
 1 pass · 2 fail   ← empty-placeholder and keeps-real-title tests fail as expected

This confirms the new tests properly validate the fix rather than being tautological.

Full suite — no new regressions

shared:      2289 pass · 15 fail · 11 skip  (all 15 failures are pre-existing:
             i18n locale parity, PowerShell plans, interceptor packaging,
             SAFE_MODE_CONFIG, safe-mode classification — none path/session related)

server-core:  112 pass · 1 fail             (pre-existing: OPEN_URL protocol
             rejection — unrelated to session filtering)

Typecheck

$ bun run typecheck:shared
  (clean exit, no errors)

Real-Scenario Testing

N/A — both fixes are desktop-internal logic (Electron-process expandPath() and SessionManager mirror filtering), unreachable from the CLI. The PR correctly marks this as "N/A — non-visible logic/path-handling fixes." Unit tests above comprehensively cover the behavior.

中文说明

代码审查

对照我的独立方案审查了 diff。PR 的实现与我的思路一致:

路径展开~\ 分支配合 .split(/[/\\]+/)storage.ts/qwen-agent.ts 中已有的双分隔符处理一致。空安全,正确处理 ~\~\foo\bar~\\(裸路径),同时保留已有的 ~/~$HOME${HOME} 行为。干净。

会话过滤 — 核心判断正确:时间戳短路是虚假的"保留"信号,因为每个镜像都有时间戳。移除它并委托给 hasNoRenderableLocalMessages() 是正确的修复。空安全加固(managed.messages?.length ?? 0typeof managed.name === 'string')对畸形记录是好的防御性编码。

无正确性 bug、无安全隐患、无 AGENTS.md 违规。Diff 聚焦——4 个文件、+172/-12,无顺手重构。

测试结果

新增测试(PR 添加)— 全部 7 个通过 ✅

路径展开 4/4 通过,会话过滤 3/3 通过(见上方输出)。

回归检查 — 基线分支确认测试能捕捉 bug

暂存 PR 修复后重跑新测试:路径测试 2 通过 2 失败,会话测试 1 通过 2 失败——确认新测试确实验证了修复,而非自证正确。

完整套件 — 无新增回归

shared: 2289 通过 · 15 失败(均为预存问题,与路径/会话无关)。server-core: 112 通过 · 1 失败(预存问题,与会话过滤无关)。

类型检查

typecheck:shared 通过,无错误。

真实场景测试

N/A — 两处修复均为桌面端内部逻辑(Electron 进程的 expandPath()SessionManager 镜像过滤),CLI 无法触达。PR 正确标注为"N/A — 不可见逻辑/路径处理修复"。上方单元测试已全面覆盖相关行为。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

This is a clean, focused bug fix. Two real Windows desktop bugs, two minimal code changes, two well-written test files. The approach matches what I'd do independently, the tests actually catch the bugs (verified against the base branch), and there's nothing in the diff that doesn't belong.

The prior CHANGES_REQUESTED review from our bot was about template formatting — the author has since restructured the PR body to follow the template correctly. That concern is resolved.

Approving. ✅

中文说明

这是一个干净、聚焦的 bug 修复。两个真实的 Windows 桌面端 bug,两处最小化的代码改动,两个编写良好的测试文件。方案与我的独立提案一致,测试确实能捕捉 bug(已与基线分支验证),diff 中每一处改动都有其必要性。

之前 bot 的 CHANGES_REQUESTED 审查是关于模板格式——作者已按模板重新组织了 PR 正文,该问题已解决。

批准 ✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@wenshao
wenshao merged commit 0d15e89 into QwenLM:main Jun 18, 2026
31 checks passed
@mvanhorn

Copy link
Copy Markdown
Contributor Author

Appreciate the quick merge, @wenshao. Expanding the Windows home-path tilde and hiding the phantom entries cleans up the desktop client.

@mvanhorn

Copy link
Copy Markdown
Contributor Author

Thanks @wenshao - expanding Windows home paths and hiding the phantom session entries are both nice quality-of-life fixes for the desktop client.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

在 Windows 桌面端中,使用某些 skill / tool 任务后,会在会话列表里额外出现一个或多个名为 (session) 的对话。

3 participants