Skip to content
This repository was archived by the owner on Jul 30, 2026. It is now read-only.

fix(core): use Claude cwd for workspace grouping - #30

Merged
makoMakoGo merged 5 commits into
personal/local-clientsfrom
codex/claude-cwd-workspace
Jun 5, 2026
Merged

fix(core): use Claude cwd for workspace grouping#30
makoMakoGo merged 5 commits into
personal/local-clientsfrom
codex/claude-cwd-workspace

Conversation

@makoMakoGo

@makoMakoGo makoMakoGo commented Jun 5, 2026

Copy link
Copy Markdown
Owner

背景

修复 Claude Code / Codex / Pi 等本地客户端在 workspace,model 分组下没有稳定归一到同一个 workspace 的问题。核心原因是 Claude Code JSONL 已经带有真实 cwd,但之前主要从 .claude/projects/... 目录名推断 workspace,容易把 -home-travis-01-workspace-... 这种编码路径当成独立 workspace。

关联 issue:#29

这次变更

  • Claude Code session 解析优先使用 entry 级 cwd 生成 workspace key/label,路径目录名只作为 fallback。
  • 修复 Claude 去重场景:如果首条重复消息没有 cwd、后续重复消息带 cwd,会把真实 workspace 回写到已合并消息。
  • 保留“有 workspace key 但 label 推不出来”的旧语义,不再因为 label 缺失而吞掉 key。
  • group/source 这类会改变聚合语义的 TUI 切换改为 blocking loading,避免用新 group_by 渲染旧聚合数据导致一排 Unknown workspace
  • 重构 Models / Daily detail 的响应式列布局为 profile 策略,继续复用 choose_priority_columns
    • required columns 统一为 Model + Tokens
    • Cost 改为最高优先级 optional
    • workspace/model 仅使用更大的 model column cap,目前为 56
  • 自用 CI 分支只保留两个 native build target,并允许 test-only workflow 是 publish matrix 的 target subset。

验证

  • cargo test:1653 passed,2 ignored
  • cargo build --release -p tokscale-cli
  • 已用本分支 release binary 覆盖 /home/travis/.local/bin/tokscale
  • tokscale --versiontokscale 3.0.0

Review 处理

  • Sourcery:已修复 workspace_parts_from_key 丢 key 的语义回归,并补测试覆盖 key 存在但 label 缺失的情况。
  • CodeRabbit:已修复 Claude 去重分支不回写后到 cwd 的问题,并补“首条无 cwd、后续重复条目有 cwd”的回归测试。
  • Gemini:已按性能建议延迟 workspace fallback clone,只在 tool context 或新消息真正需要 owned options 时生成,同时保留 key/label 分离语义。

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

总体说明

此 PR 解决了 Claude Code 工作区分组的规范化问题。Claude Code 将会话存储在编码的项目目录下(如 ~/.claude/projects/-home-travis-01-workspace/),但行级数据包含真实的 cwd;而 Codex/Pi 直接使用 cwd 路径。当前实现导致同一物理工作区被分裂到多个分组桶。此 PR 通过优先使用条目级别 cwd 来规范化工作区身份,使多个客户端能在 group-by workspace,model 时正确聚合。

变更说明

工作区元数据基础设施
crates/tokscale-core/src/sessions/mod.rs

在 sessions 模块中引入 WorkspaceMetadata 结构体及 workspace_metadata_from_key 函数,统一处理路径归一化与 label 推导,并提供单元测试验证 Windows 路径的正斜杠转换。

Claude Code 条目级工作区支持
crates/tokscale-core/src/sessions/claudecode.rs

  • ClaudeEntry 添加 cwd: Option<String> 字段以携带条目级工作区信息
  • 更新模块导入,改用新的 workspace_metadata_from_key 函数
  • 在 JSONL 解析主循环中,优先从 entry.cwd 推导 current_workspace_key/label,不可用时回退到文件路径推导
  • assistant 消息与 tool_result 上下文均使用按条目推导的工作区
  • 重构 claude_workspace_from_path 以使用新的元数据函数
  • 新增单元测试验证 cwd 优先级与 Windows 路径归一化行为

缓存 schema 版本更新
crates/tokscale-cli/src/tui/cache.rs, crates/tokscale-core/src/message_cache.rs, crates/tokscale-cli/tests/cli_tests.rs

  • TUI 缓存版本从 12 升级至 13,消息缓存版本从 17 升级至 18
  • 同步更新单元测试 JSON 固定数据中的 schemaVersion 字段

多客户端工作区聚合集成测试
crates/tokscale-cli/tests/cli_tests.rs

新增 create_mixed_workspace_fixture_dir 测试夹具,为同一工作区路径创建 Claude/Codex/Pi 三类会话数据,并新增集成测试验证 workspace,model 分组后的正确聚合行为。

可能相关的 PR

  • makoMakoGo/tokscale#14:同样修改了 TUI 磁盘缓存 CACHE_SCHEMA_VERSION 常量(伴随缓存 JSON 与测试 fixture 的更新),但不包含此 PR 中关于 Claude cwd 工作区元数据的其他核心变更。

估计审查工作量

🎯 3 (中等) | ⏱️ ~20 分钟

此 PR 涉及多个文件的协调变更,但单个变更的逻辑密度不高。工作区元数据基础设施相对直观,Claude 解析的改动遵循清晰的优先级逻辑(条目级 cwd > 文件路径推导),缓存版本更新是机械性的,集成测试验证了关键行为。需要理解工作区规范化的业务背景,并跟踪条目级工作区信息在消息对象链中的传递路径。

🐰 在 cwd 的光辉下,
Claude 与 Codex 携手行走,
工作区如一,聚合成真!
缓存失效重生,新的秩序降临,
兔子为你见证这场工作区的统一 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确总结了主要变更:使用 Claude cwd 进行工作区分组,与所有文件变更保持一致。
Linked Issues check ✅ Passed 代码变更完整满足 issue #29 的所有接受标准:优先使用 cwd 进行工作区分组、新增 WorkspaceMetadata 统一处理、支持 Windows 路径归一化、混合客户端合并、缓存失效处理,并包含完整测试覆盖。
Out of Scope Changes check ✅ Passed 所有变更均与 issue #29 的工作区分组需求直接相关,包括缓存更新和相应测试,无超出范围的变更。
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/claude-cwd-workspace

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai

sourcery-ai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Improves workspace grouping by preferring Claude Code cwd-derived workspace metadata, centralizing workspace key/label normalization, and invalidating caches so mixed-client sessions (Claude, Codex, Pi) merge correctly by workspace.

Flow diagram for cwd-preferred workspace metadata selection

flowchart TD
    A[ClaudeEntry parsed] --> B{entry.cwd is Some}
    B -- yes --> C[workspace_metadata_from_key cwd]
    C --> D{WorkspaceMetadata returned}
    D -- yes --> E[set current_workspace_key and current_workspace_label from WorkspaceMetadata]
    D -- no --> F[use workspace_key and workspace_label from claude_workspace_from_path]
    B -- no --> F
    E --> G[parse_claude_file_with_cache_and_home builds UnifiedMessage with current workspace]
    F --> G
Loading

File-Level Changes

Change Details Files
Prefer Claude Code cwd-based workspace metadata and unify workspace key/label derivation.
  • Extend ClaudeEntry to capture cwd from Claude Code JSONL entries.
  • When parsing Claude sessions, derive workspace key/label from entry.cwd when present, falling back to existing project-path-derived values.
  • Use normalized workspace metadata (key and label) when setting workspace on unified messages and sidechains.
  • Refactor Claude project-path parsing to use a shared workspace_parts_from_key helper that relies on workspace_metadata_from_key.
crates/tokscale-core/src/sessions/claudecode.rs
Introduce shared WorkspaceMetadata helper for consistent workspace key/label normalization.
  • Add WorkspaceMetadata struct encapsulating normalized workspace key and label.
  • Implement workspace_metadata_from_key to derive a normalized workspace key and label pair from raw workspace identifiers.
  • Add tests to ensure workspace_metadata_from_key correctly normalizes Windows-style paths and derives labels.
crates/tokscale-core/src/sessions/mod.rs
Ensure CLI models command merges Claude, Codex, and Pi sessions by shared cwd in workspace grouping.
  • Add a mixed workspace fixture that creates Claude, Codex, and Pi sessions sharing a common cwd.
  • Add a CLI integration test asserting that models --group-by workspace,model produces a single merged workspace entry for mixed clients and aggregates usage and message counts.
  • Verify mergedClients field lists all contributing clients (claude, codex, pi) for the shared workspace.
crates/tokscale-cli/tests/cli_tests.rs
Invalidate TUI and source-message caches to rebuild workspace grouping with updated metadata.
  • Bump TUI cache schema version from 12 to 13 and update corresponding test fixtures to expect the new version.
  • Bump source-message cache schema version from 17 to 18 to invalidate existing cache files.
  • Rely on schema versioning to trigger regeneration of caches with the new workspace grouping semantics.
crates/tokscale-cli/src/tui/cache.rs
crates/tokscale-core/src/message_cache.rs

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request enhances workspace metadata handling for Claude Code sessions by extracting and preferring the cwd field from individual log entries over the directory path. This allows for accurate workspace grouping across different clients (Claude, Codex, and Pi). The changes also include cache schema version bumps, helper utilities, and comprehensive tests. The reviewer feedback focuses on optimizing parsing performance by deferring and inlining the workspace key and label computations to avoid unnecessary string allocations and cloning on every line of the JSONL files.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/tokscale-core/src/sessions/claudecode.rs Outdated
Comment thread crates/tokscale-core/src/sessions/claudecode.rs Outdated
Comment thread crates/tokscale-core/src/sessions/claudecode.rs Outdated
@makoMakoGo
makoMakoGo marked this pull request as ready for review June 5, 2026 14:06

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In workspace_parts_from_key, you now drop the workspace key entirely when workspace_label_from_key returns None, whereas the previous logic would still preserve a normalized key with a None label; if there are edge cases where a label cannot be derived (e.g., root paths), consider keeping the key even when the label is missing to avoid silently losing workspace identity.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `workspace_parts_from_key`, you now drop the workspace key entirely when `workspace_label_from_key` returns `None`, whereas the previous logic would still preserve a normalized key with a `None` label; if there are edge cases where a label cannot be derived (e.g., root paths), consider keeping the key even when the label is missing to avoid silently losing workspace identity.

## Individual Comments

### Comment 1
<location path="crates/tokscale-core/src/sessions/claudecode.rs" line_range="666-675" />
<code_context>
-            let key = normalize_workspace_key(&window[2]);
-            let label = key.as_deref().and_then(workspace_label_from_key);
-            return (key, label);
+            return workspace_parts_from_key(&window[2]);
         }
     }

     for window in components.windows(5) {
         if window[0] == ".cc-mirror" && window[2] == "config" && window[3] == "projects" {
-            let key = normalize_workspace_key(&window[4]);
-            let label = key.as_deref().and_then(workspace_label_from_key);
-            return (key, label);
+            return workspace_parts_from_key(&window[4]);
         }
     }

     for window in components.windows(2).rev() {
         if window[0] == "projects" {
-            let key = normalize_workspace_key(&window[1]);
-            let label = key.as_deref().and_then(workspace_label_from_key);
-            return (key, label);
+            return workspace_parts_from_key(&window[1]);
         }
     }
</code_context>
<issue_to_address>
**issue (bug_risk):** Behavior change: keys are now dropped when a label cannot be derived

With the previous implementation, callers could still get a normalized key even if `workspace_label_from_key` failed; now `workspace_metadata_from_key` returns `None` in that case and `unwrap_or((None, None))` drops the key as well. If any callers depend on having a key when the label is missing, this could break existing behavior. If this change is intentional, consider auditing and documenting those call sites so the new semantics are explicit and surprises are avoided.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread crates/tokscale-core/src/sessions/claudecode.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/tokscale-core/src/sessions/claudecode.rs (1)

2255-2287: ⚡ Quick win

建议补一个“首条无 cwd、后续重复条目有 cwd”回归测试

现有新增测试覆盖了“有 cwd 时优先于项目目录名”,但未覆盖去重场景下 cwd 后到达的情况。补这个用例可以直接防止上述回归。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tokscale-core/src/sessions/claudecode.rs` around lines 2255 - 2287,
Add a regression test that covers the case where the first session entry lacks a
cwd but a subsequent duplicate entry contains a cwd: create a new #[test] (e.g.,
test_workspace_metadata_prefers_later_entry_cwd_on_dedupe) which writes a
session.jsonl with two messages (first without "cwd", second with the same
content but including "cwd") using create_project_file, call
parse_claude_file(&path), and assert that messages.len() == 1 (deduped) and that
messages[0].workspace_key and messages[0].workspace_label reflect the cwd from
the later entry (use the same assertion pattern as existing tests to check
workspace_key and workspace_label).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/tokscale-core/src/sessions/claudecode.rs`:
- Around line 404-412: 去重分支在合并 token/provider 后直接 continue,导致如果首条重复项没有 cwd
而后续重复项有 cwd 时不会回写 workspace 信息;在合并重复块(涉及
entry、entry_workspace、workspace_metadata_from_key、workspace_key、workspace_label)之后应同步
workspace:检查被合并的 entry.cwd(或其 workspace_metadata_from_key 返回值),若存在则将目标条目的
workspace key/label 更新为该值(即在合并 token/provider 的分支里在 continue 前或替代 continue 的位置回写
current_workspace_key/current_workspace_label 或直接更新目标 entry 的 workspace
字段);同样修复在文件中其它类似去重分支(也适用于 458-467、508-560 区段)。

---

Nitpick comments:
In `@crates/tokscale-core/src/sessions/claudecode.rs`:
- Around line 2255-2287: Add a regression test that covers the case where the
first session entry lacks a cwd but a subsequent duplicate entry contains a cwd:
create a new #[test] (e.g.,
test_workspace_metadata_prefers_later_entry_cwd_on_dedupe) which writes a
session.jsonl with two messages (first without "cwd", second with the same
content but including "cwd") using create_project_file, call
parse_claude_file(&path), and assert that messages.len() == 1 (deduped) and that
messages[0].workspace_key and messages[0].workspace_label reflect the cwd from
the later entry (use the same assertion pattern as existing tests to check
workspace_key and workspace_label).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b887933b-a21c-4c7c-883d-bfef0c047d3a

📥 Commits

Reviewing files that changed from the base of the PR and between a7fd4e7 and a9a1eba.

📒 Files selected for processing (5)
  • crates/tokscale-cli/src/tui/cache.rs
  • crates/tokscale-cli/tests/cli_tests.rs
  • crates/tokscale-core/src/message_cache.rs
  • crates/tokscale-core/src/sessions/claudecode.rs
  • crates/tokscale-core/src/sessions/mod.rs

Comment thread crates/tokscale-core/src/sessions/claudecode.rs Outdated
@makoMakoGo
makoMakoGo merged commit b1c03d8 into personal/local-clients Jun 5, 2026
4 checks passed
@makoMakoGo
makoMakoGo deleted the codex/claude-cwd-workspace branch June 5, 2026 16:05
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant