Skip to content

feat: 工作区缓存层 + Agent 自动重启,确保 CLAUDE.md 正确加载 - #9

Merged
lishuceo merged 12 commits into
mainfrom
feat/workspace-restart-phase1
Feb 17, 2026
Merged

feat: 工作区缓存层 + Agent 自动重启,确保 CLAUDE.md 正确加载#9
lishuceo merged 12 commits into
mainfrom
feat/workspace-restart-phase1

Conversation

@lishuceo

Copy link
Copy Markdown
Owner

Summary

  • Agent 自动重启机制:当 setup_workspace MCP tool 在 query 运行中切换工作目录后,自动以新 cwd 发起第二次 query,确保 CLAUDE.md 从一开始就生效
  • Bare clone 缓存层:维护本地 bare clone 镜像,readonly/writable 模式都通过 local clone 获得独立工作树,秒级完成
  • readonly/writable 模式:Claude 根据语义自主选择 mode 参数,只读分析不创建分支,修改代码才创建 feature branch
  • 安全加固:git clone/fetch 禁用 hooks/submodules/file 协议,URL 路径穿越校验,认证信息剥离
  • taskQueue 集成:修复同一 chat 的 query 未串行化的并发问题

Changes

文件 说明
src/workspace/cache.ts 新增:bare clone 缓存管理(URL 解析、路径穿越校验、缓存 CRUD、原子创建、过期清理)
src/workspace/manager.ts 增加 mode 参数,接入缓存层,writable 模式剥离 URL 认证信息
src/workspace/tool.ts MCP tool schema 增加 mode 参数
src/claude/executor.ts ExecuteOptions、restart 信号、disableWorkspaceTool、system prompt mode 引导
src/claude/types.ts ClaudeResult 增加 restart 字段、新增 ExecuteOptions
src/feishu/event-handler.ts taskQueue 集成、restart 逻辑、sendResultCard 提取
src/config.ts 新增 repoCache 配置组
src/index.ts 启动时清理 tmp、定时清理过期缓存
docs/workspace-cache-and-restart.md 设计文档

Follow-up

以下运维健壮性增强项留作后续实现:

  • flock 并发控制(fetch 与 clone 互斥)
  • 基于总大小的 LRU 缓存清理(REPO_CACHE_MAX_SIZE_GB)
  • session 过期联动删除工作区磁盘目录

Test plan

  • 112 个单元测试全部通过(新增 52 个)
  • TypeScript 类型检查通过
  • 手动验证:发送包含 GitHub URL 的消息,确认 workspace setup → restart → CLAUDE.md 加载
  • 手动验证:readonly 模式不创建 feature branch
  • 手动验证:第二次访问同一仓库走 bare cache(秒级 clone)

🤖 Generated with Claude Code

lishuceo and others added 5 commits February 17, 2026 18:21
解决首次访问仓库时 CLAUDE.md 不生效的问题:当 setup_workspace MCP tool
在 query 运行中切换工作目录后,自动以新 cwd 发起第二次 query,让 Agent SDK
从一开始就加载目标仓库的项目配置。

主要变更:
- executor 支持 restart 信号 (needsRestart/newWorkingDir) 和 ExecuteOptions
  (maxTurns/maxBudgetUsd/disableWorkspaceTool)
- event-handler 检测 restart 信号后清空 conversationId、更新进度卡片、
  以新 cwd 重新执行(移除 setup_workspace tool 防止循环)
- 集成 taskQueue 串行化同一 chat 的 query 执行
- git clone 增加安全参数 (禁用 hooks/submodules/file 协议)
- system prompt 引导 Claude 在调用 setup_workspace 后立即结束
- 附设计文档 docs/workspace-cache-and-restart.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
新增测试:
- executor: restart 信号传递、disableWorkspaceTool、options 覆盖、回调包装
- event-handler: restart 流程(检测信号→清空 conversationId→重新执行)、
  abort 检查(session 不再 busy 时取消 restart)
- workspace/manager: git clone 安全参数验证(hooks/submodules/file 协议)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
引入仓库 bare clone 缓存层,优化 clone 速度和只读查询体验:

- 新增 src/workspace/cache.ts:URL 解析与路径穿越校验、bare clone
  缓存创建/更新(带 fetch interval 控制)、原子目录创建
  (tmp + rename)、过期缓存清理(LRU + maxAgeDays)、启动时
  清理 .tmp-* 残留
- 改造 setupWorkspace 支持 mode 参数:readonly 从缓存 local clone
  不创建分支,writable 从缓存 local clone + 创建 feature branch +
  git remote set-url(剥离认证信息)
- MCP tool schema 增加 mode 参数,system prompt 增加模式选择引导
- 新增 REPO_CACHE_DIR / REPO_CACHE_MAX_AGE_DAYS /
  REPO_CACHE_MAX_SIZE_GB / REPO_CACHE_FETCH_INTERVAL_MIN 配置项
- 缓存清理集成到 30 分钟 cleanup interval,启动时清理临时目录
- 新增 cache.test.ts (22 tests),更新 manager.test.ts (30 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
不人为限制第一次 query 的 maxTurns/maxBudgetUsd,确保不需要
setup_workspace 时也能完整执行任务,追求质量而非节省成本。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
解决与 #8 (fix: 话题路由和会话恢复消息修正) 的合并冲突:
- QueueTask 增加 rootId 字段
- TaskQueue.enqueue 接受 rootId 参数
- processQueue 将 rootId 传递给 executeClaudeTask

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Feb 17, 2026

Copy link
Copy Markdown

Claude finished @lishuceo's task in 6m 5s —— View job


Code Review: ⚠️ Issues Found

Reviewed all 16 changed files across the bare clone cache layer, agent restart mechanism, and taskQueue integration.


Issues

  • 🔴 Unhandled promise rejection in event-handler.ts (Confidence: 90) — taskQueue.enqueue() returns a Promise that is never awaited or .catch()-ed. When the task rejects, this causes an unhandled promise rejection that can crash the Node.js process. See comment →

  • 🟡 Cache cleanup operates at wrong directory level in cache.ts (Confidence: 80) — cleanupExpiredCaches() only descends 2 levels (host/owner), but cache paths are 3 levels deep (host/owner/repo.git). This causes cleanup to check the owner directory's atime instead of individual repo caches, potentially deleting all repos under an owner or missing stale individual repos. See comment →

  • 🟡 Dead code / inconsistent branch value in manager.ts (Confidence: 85) — The branch variable (line 109) is computed but unused in the writable path (shadowed by branchName on line 159). The readonly return uses 'default' instead of the computed 'HEAD', creating an inconsistency. See comment →


Overall Assessment

The architecture is well-designed — the bare clone cache layer, atomic creation with tmp+rename, and the restart mechanism for CLAUDE.md loading are solid patterns. The security hardening (hooks disabled, submodules blocked, path traversal checks, credential stripping) is thorough. The sendResultCard extraction and cost/duration merging across restart queries are clean.

The critical issue is the unhandled promise rejection which can crash the process in production. The cache cleanup granularity bug would cause incorrect cleanup behavior but is lower severity since it only affects the periodic maintenance path. Both should be fixed before merge.

Comment thread src/feishu/event-handler.ts Outdated
Comment thread src/workspace/cache.ts Outdated
Comment thread src/workspace/manager.ts Outdated
- 修复 enqueue 返回的 Promise 未处理导致的 unhandled rejection
- 修复缓存清理只遍历 2 级目录的问题,改为递归查找 .git 结尾的
  bare clone 目录(缓存路径为 host/owner/repo.git 共 3 级)
- 删除 manager.ts 中无用的 branch 变量(被 branchName 遮蔽)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Feb 17, 2026

Copy link
Copy Markdown

Claude finished @lishuceo's task in 3m 4s —— View job


Code Review: ✅ All Previous Issues Fixed

Reviewed all 16 changed files after the latest commit (0fa5f71).


Previous Issues — All Resolved

Issue Status
🔴 Unhandled promise rejection in event-handler.ts ✅ Fixed — .catch(() => {}) added at line 189
🟡 Cache cleanup at wrong directory level in cache.ts ✅ Fixed — recursive cleanupExpiredRecursive now correctly identifies .git directories as cache units
🟡 Dead code / inconsistent branch in manager.ts ✅ Fixed — unused branch variable removed, readonly path returns `sourceBranch

New Issues

No new issues found with confidence ≥ 75.

Overall Assessment

All three issues from the previous review have been properly addressed. The code is clean:

  • Restart mechanism (event-handler.ts): The two-phase query flow with disableWorkspaceTool to prevent infinite loops, session status checks before restart, and cost/duration merging across queries is well-implemented.
  • Bare clone cache (cache.ts): Atomic creation via tmp+rename, path traversal protection, recursive cleanup targeting .git directories, and stale-fetch tolerance are all solid.
  • Security (manager.ts, cache.ts): Git hooks disabled, submodules blocked, file protocol restricted, credential stripping via sanitizeRepoUrl, branch name validation — thorough coverage.
  • Queue integration (event-handler.ts, queue.ts): Promise rejection properly handled, processQueue drains correctly via .finally() chaining.

This PR is ready for merge from a code quality perspective.

lishuceo and others added 6 commits February 17, 2026 23:15
之前 Claude review 发现问题已修复时只 reply "✅ Fixed" 但不会
resolve conversation thread,导致 PR 页面上仍显示 unresolved。

增加 Step 5:通过 GraphQL API 查询 review thread ID 并 resolve。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
🔴 Critical:
- 拆分 GIT_SECURITY_ARGS 为远程/本地两组(git-security.ts),
  本地 clone 不禁 file 协议,修复缓存加速功能失效的 blocker

🟡 Warning:
- localPath 增加基目录白名单校验(必须在 DEFAULT_WORK_DIR 下)
- fetchIfStale 补上 core.hooksPath=/dev/null,与 clone 一致
- atimeMs 改为 mtimeMs,避免 noatime/relatime 挂载下误判
- restart 失败时 conversationId 使用第一次 query 的 sessionId 作为 fallback
- maxSizeGb 配置项标注 TODO(尚未实现 LRU 大小清理)

🔵 Info:
- GIT_SECURITY_ARGS 提取到 git-security.ts 共享模块,消除重复定义

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fetchIfStale 改用共享 GIT_REMOTE_SECURITY_ARGS 常量,避免
  安全参数分散定义不一致
- manager.ts 统一使用 GIT_LOCAL_SECURITY_ARGS(clone 源总是
  本地路径:bare cache 或 localPath),修复 localPath clone
  因 protocol.file.allow=never 失败的 bug
- /project 命令增加路径白名单校验,与 localPath 安全策略一致
- error catch 回复消息优先发到话题内,而非孤立回复到主聊天区

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- localPath 和 /project 命令路径校验改用 realpathSync 跟踪 symlink,
  防止 symlink 指向 allowedBase 外的敏感目录
- restart 前验证 newWorkingDir 是否实际存在,不存在时取消 restart
  并向用户返回错误卡片
- cache.ts 和 manager.ts 日志中的 repoUrl 改用 sanitizeRepoUrl
  输出,避免泄露 URL 中嵌入的认证信息

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- git-security.ts 拆分为 clone 专用 (--config) 和 fetch 专用 (-c)
  两组参数,修复 --config 作为 git 顶层选项导致 fetch 静默失败的 bug
- manager.ts: realpathSync(defaultWorkDir) 在目录不存在时降级为 resolve
- event-handler.ts: /project 命令存储 realpathSync 规范化后的绝对路径

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
新增 pr-fixup skill,在 PR 提交后自动轮询等待 pr-review action
完成,分析 claude[bot] 的 review 评论,修复真实问题或反驳误报
并 resolve thread,循环直到 PR 无阻塞问题。同时更新 /ship skill
在 PR 创建后提示用户可运行 /pr-fixup。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lishuceo
lishuceo merged commit 55739b2 into main Feb 17, 2026
1 check passed
@claude

claude Bot commented Feb 17, 2026

Copy link
Copy Markdown

Claude finished @lishuceo's task in 2m 8s —— View job


Code Review: ✅ No Issues Found

  • Check and resolve previous review comments
  • Read current diff and all changed files
  • Review for bugs, security, architecture issues
  • Post final summary

Reviewed all changed source files across the bare clone cache layer (cache.ts, git-security.ts), workspace manager (manager.ts, tool.ts), agent restart mechanism (executor.ts, event-handler.ts), queue integration (queue.ts), and configuration (config.ts, index.ts, types.ts).

Previous Issues — All Resolved

3 previous issues fixed and threads resolved:

Issue Status
🔴 Unhandled promise rejection in event-handler.ts ✅ Fixed — .catch(() => {}) added
🟡 Cache cleanup at wrong directory level in cache.ts ✅ Fixed — recursive cleanup targets .git directories
🟡 Dead code / inconsistent branch in manager.ts ✅ Fixed — unused variable removed

New Issues

No new issues found with confidence ≥ 75.

Assessment

The merged code is clean. Key observations:

  • Queue serialization (event-handler.ts:86-98): processQueue correctly uses dequeue's running guard + .finally() chaining to ensure one-at-a-time execution per chat, with the next task picked up automatically.
  • Restart loop prevention (executor.ts:99-101): disableWorkspaceTool completely removes the MCP tool on restart, making infinite loops impossible.
  • Atomic cache creation (cache.ts:130-158): tmp directory + renameSync pattern is solid. Concurrent bare clones for the same repo would result in one overwriting the other via atomic rename (safe on Linux), and the follow-up notes flock for optimization.
  • Security (git-security.ts, cache.ts:64-70, manager.ts:84-91): execFileSync prevents command injection, path traversal is guarded at multiple layers (segment validation + resolve prefix check + realpathSync for symlinks), credentials are stripped from logged/stored URLs.
  • Cost merging (event-handler.ts:522-523): Correctly handles undefined costs with ?? 0 fallback across both query phases.

@lishuceo
lishuceo deleted the feat/workspace-restart-phase1 branch February 17, 2026 17:27
lishuceo added a commit that referenced this pull request Feb 17, 2026
解决冲突:
- executor.ts: 保留 options 对象模式 (ExecuteInput),合入 main 的
  restart 信号跟踪 + disableWorkspaceTool + maxTurns/maxBudgetUsd
- event-handler.ts: 合入 main 的 restart 逻辑 + sendResultCard +
  /project 路径安全校验 + processQueue,保留我们的 pipeline +
  acquireSession + /dev 命令
- executor.test.ts: 更新测试从位置参数改为 ExecuteInput 对象模式

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lishuceo added a commit that referenced this pull request Apr 8, 2026
* feat: workspace 变更后自动重启 agent,确保 CLAUDE.md 正确加载

解决首次访问仓库时 CLAUDE.md 不生效的问题:当 setup_workspace MCP tool
在 query 运行中切换工作目录后,自动以新 cwd 发起第二次 query,让 Agent SDK
从一开始就加载目标仓库的项目配置。

主要变更:
- executor 支持 restart 信号 (needsRestart/newWorkingDir) 和 ExecuteOptions
  (maxTurns/maxBudgetUsd/disableWorkspaceTool)
- event-handler 检测 restart 信号后清空 conversationId、更新进度卡片、
  以新 cwd 重新执行(移除 setup_workspace tool 防止循环)
- 集成 taskQueue 串行化同一 chat 的 query 执行
- git clone 增加安全参数 (禁用 hooks/submodules/file 协议)
- system prompt 引导 Claude 在调用 setup_workspace 后立即结束
- 附设计文档 docs/workspace-cache-and-restart.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: 添加 Phase 1 restart 机制和 git 安全参数的单元测试

新增测试:
- executor: restart 信号传递、disableWorkspaceTool、options 覆盖、回调包装
- event-handler: restart 流程(检测信号→清空 conversationId→重新执行)、
  abort 检查(session 不再 busy 时取消 restart)
- workspace/manager: git clone 安全参数验证(hooks/submodules/file 协议)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: bare clone 缓存层 + readonly/writable 工作区模式 (Phase 2)

引入仓库 bare clone 缓存层,优化 clone 速度和只读查询体验:

- 新增 src/workspace/cache.ts:URL 解析与路径穿越校验、bare clone
  缓存创建/更新(带 fetch interval 控制)、原子目录创建
  (tmp + rename)、过期缓存清理(LRU + maxAgeDays)、启动时
  清理 .tmp-* 残留
- 改造 setupWorkspace 支持 mode 参数:readonly 从缓存 local clone
  不创建分支,writable 从缓存 local clone + 创建 feature branch +
  git remote set-url(剥离认证信息)
- MCP tool schema 增加 mode 参数,system prompt 增加模式选择引导
- 新增 REPO_CACHE_DIR / REPO_CACHE_MAX_AGE_DAYS /
  REPO_CACHE_MAX_SIZE_GB / REPO_CACHE_FETCH_INTERVAL_MIN 配置项
- 缓存清理集成到 30 分钟 cleanup interval,启动时清理临时目录
- 新增 cache.test.ts (22 tests),更新 manager.test.ts (30 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: 更新设计文档,移除第一次 query 的 turns/budget 限制

不人为限制第一次 query 的 maxTurns/maxBudgetUsd,确保不需要
setup_workspace 时也能完整执行任务,追求质量而非节省成本。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 修复 PR review 发现的 3 个问题

- 修复 enqueue 返回的 Promise 未处理导致的 unhandled rejection
- 修复缓存清理只遍历 2 级目录的问题,改为递归查找 .git 结尾的
  bare clone 目录(缓存路径为 host/owner/repo.git 共 3 级)
- 删除 manager.ts 中无用的 branch 变量(被 branchName 遮蔽)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: PR review workflow 修复后自动 resolve conversation

之前 Claude review 发现问题已修复时只 reply "✅ Fixed" 但不会
resolve conversation thread,导致 PR 页面上仍显示 unresolved。

增加 Step 5:通过 GraphQL API 查询 review thread ID 并 resolve。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 修复 deep-review 发现的 7 个问题

🔴 Critical:
- 拆分 GIT_SECURITY_ARGS 为远程/本地两组(git-security.ts),
  本地 clone 不禁 file 协议,修复缓存加速功能失效的 blocker

🟡 Warning:
- localPath 增加基目录白名单校验(必须在 DEFAULT_WORK_DIR 下)
- fetchIfStale 补上 core.hooksPath=/dev/null,与 clone 一致
- atimeMs 改为 mtimeMs,避免 noatime/relatime 挂载下误判
- restart 失败时 conversationId 使用第一次 query 的 sessionId 作为 fallback
- maxSizeGb 配置项标注 TODO(尚未实现 LRU 大小清理)

🔵 Info:
- GIT_SECURITY_ARGS 提取到 git-security.ts 共享模块,消除重复定义

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 修复 deep-review v2 发现的 4 个问题

- fetchIfStale 改用共享 GIT_REMOTE_SECURITY_ARGS 常量,避免
  安全参数分散定义不一致
- manager.ts 统一使用 GIT_LOCAL_SECURITY_ARGS(clone 源总是
  本地路径:bare cache 或 localPath),修复 localPath clone
  因 protocol.file.allow=never 失败的 bug
- /project 命令增加路径白名单校验,与 localPath 安全策略一致
- error catch 回复消息优先发到话题内,而非孤立回复到主聊天区

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: symlink 路径穿越防护、restart 目录验证、日志凭据脱敏

- localPath 和 /project 命令路径校验改用 realpathSync 跟踪 symlink,
  防止 symlink 指向 allowedBase 外的敏感目录
- restart 前验证 newWorkingDir 是否实际存在,不存在时取消 restart
  并向用户返回错误卡片
- cache.ts 和 manager.ts 日志中的 repoUrl 改用 sanitizeRepoUrl
  输出,避免泄露 URL 中嵌入的认证信息

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: git 安全参数 --config/-c 混用、realpathSync 崩溃、路径规范化

- git-security.ts 拆分为 clone 专用 (--config) 和 fetch 专用 (-c)
  两组参数,修复 --config 作为 git 顶层选项导致 fetch 静默失败的 bug
- manager.ts: realpathSync(defaultWorkDir) 在目录不存在时降级为 resolve
- event-handler.ts: /project 命令存储 realpathSync 规范化后的绝对路径

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: 添加 /pr-fixup skill — 自动等待 review 并修复问题

新增 pr-fixup skill,在 PR 提交后自动轮询等待 pr-review action
完成,分析 claude[bot] 的 review 评论,修复真实问题或反驳误报
并 resolve thread,循环直到 PR 无阻塞问题。同时更新 /ship skill
在 PR 创建后提示用户可运行 /pr-fixup。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant