From a1ad254a2edf308ee6984b10dbef43dd6c47530b Mon Sep 17 00:00:00 2001 From: lishuceo Date: Mon, 8 Jun 2026 17:15:49 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=E6=94=B6=E5=88=B0=20result=20?= =?UTF-8?q?=E5=90=8E=E7=AB=8B=E5=8D=B3=E8=B7=B3=E5=87=BA=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=EF=BC=8C=E9=81=BF=E5=85=8D=20idle=20timeout?= =?UTF-8?q?=20=E8=AF=AF=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:SDK 返回 result 消息后,如果有 background task 仍在运行, for-await 循环不会结束,导致 idle timer 继续倒计时最终触发超时。 实际查询已经成功完成,不应该报超时。 修复:用 labeled break 在收到 result 后立即跳出循环, 并调用 q.close() 确保子进程不泄漏。 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/claude/executor.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/claude/executor.ts b/src/claude/executor.ts index b544fcf..29e8e80 100644 --- a/src/claude/executor.ts +++ b/src/claude/executor.ts @@ -973,7 +973,8 @@ export class ClaudeExecutor { try { // 遍历 SDK 流式消息 - for await (const message of q) { + // label 用于在收到 result 后跳出循环(result 是 SDK 协议的终止消息) + messageLoop: for await (const message of q) { // 每收到消息重置 idle 计时器 resetIdleTimer(`msg:${message.type}${'subtype' in message ? ':' + (message as Record).subtype : ''}`); @@ -1112,7 +1113,7 @@ export class ClaudeExecutor { case 'result': resultMessage = message; - break; + break messageLoop; default: // tool_progress, stream_event 等其他消息类型 — 记录以便诊断 idle timeout 间隙 @@ -1121,6 +1122,10 @@ export class ClaudeExecutor { break; } } + + // 提前 break(收到 result)时子进程可能仍在运行(如有 background task), + // 主动 close 确保不泄漏;正常迭代结束时 close 是幂等的 no-op + q.close(); } catch (err) { clearTimeout(idleTimer); if (hardTimer) clearTimeout(hardTimer); From a4ca23cbd012128b8950260f16479dd30617f21a Mon Sep 17 00:00:00 2001 From: lishuceo Date: Mon, 8 Jun 2026 17:17:41 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20settingSources=20=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=20'local'=20=E4=BB=A5=E5=8A=A0=E8=BD=BD=20settings.local.json?= =?UTF-8?q?=20=E6=9D=83=E9=99=90=E7=99=BD=E5=90=8D=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:urhox-bench 的 .claude/settings.local.json 包含完整的 bash 命令 白名单(git/npm/npx/gh 等),但 SDK 只传了 ['user', 'project'], 'local' 未包含,导致这些权限不生效。SDK 弹窗请求授权时飞书 bot 无法交互,报 "Stream closed"。 修复:settingSources 加入 'local',优先级:user < project < local。 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/claude/executor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/claude/executor.ts b/src/claude/executor.ts index 9162d49..33dcd8a 100644 --- a/src/claude/executor.ts +++ b/src/claude/executor.ts @@ -946,7 +946,8 @@ export class ClaudeExecutor { : { type: 'preset', preset: 'claude_code', append: promptAppend }, // 加载项目设置 (CLAUDE.md 等);路由 agent 传 [] 避免加载 - settingSources: settingSourcesOverride ?? ['user', 'project'], + // 'local' 加载 .claude/settings.local.json(优先级最高,覆盖 project) + settingSources: settingSourcesOverride ?? ['user', 'project', 'local'], // MCP 服务器:工作区管理工具 + 飞书工具 (空对象等同于无 MCP 服务器) mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined, From 6183b0326d52dd5ea6ed2b8e60e56f41932b83d3 Mon Sep 17 00:00:00 2001 From: lishuceo Date: Mon, 8 Jun 2026 19:32:04 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E5=9C=A8=20system=20prompt=20?= =?UTF-8?q?=E4=B8=AD=E6=B3=A8=E5=85=A5=E5=B9=B3=E5=8F=B0=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 Feishu bot 环境的运行时限制(子进程生命周期、长任务模式、权限交互) 硬编码到 buildWorkspaceSystemPrompt 中,确保所有通过 anycode 执行的 agent 都能正确理解平台约束。 之前这些约束放在 config/knowledge/platform.md(gitignored), 新部署或其他开发者无法受益。移到代码中后: - 入 git,所有开发者受益 - 不可被 config 误删 - 任何新部署自动包含 - 不影响目标仓库的 CLAUDE.md(原生 CLI/Codex 不受约束) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/claude/executor.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/claude/executor.ts b/src/claude/executor.ts index 33dcd8a..46bc392 100644 --- a/src/claude/executor.ts +++ b/src/claude/executor.ts @@ -465,7 +465,30 @@ gh search issues --repo owner/repo -- "keyword" # 错误 ✗ — 不要 cd 到主仓库或其他目录执行 gh cd /some/other/dir && gh pr view 123 -\`\`\``; +\`\`\` + +## 平台执行约束 + +你运行在 Feishu Bot 环境中,通过 Claude Agent SDK 的 \`query()\` API 执行。以下约束源于该执行模型,与目标仓库无关: + +### 子进程生命周期 + +每次对话是一次独立的 query,返回结果后子进程(包括所有后台任务)会被回收。这意味着: + +- **不要用 Monitor 等待超过 5 分钟的后台任务结果** — 你返回 result 后 Monitor 会随子进程一起终止 +- **不要期望"返回后继续工作"** — 一旦你给出最终回复,本次执行即结束 + +### 长时间任务的正确模式 + +对于需要 5 分钟以上才能完成的后台任务(如跑批、大规模测试、编译): + +1. 用 \`nohup ... &\` 或 \`... &\` 启动任务,确保脱离子进程 +2. 用 cron 工具(\`manage_cron\`)设一个一次性定时检查(\`schedule_kind: "at"\`),在预计完成时间触发 +3. 立即返回结果,告知用户任务已启动、预计完成时间、以及会自动回来检查 + +### 权限交互 + +Bot 场景无法弹窗授权。所有需要的命令权限必须通过 \`.claude/settings.json\` 或 \`.claude/settings.local.json\` 白名单预配置。如果遇到权限拒绝,不要重试,告知用户需要更新白名单。`; const selfRepoGuide = (workingDir && isServiceOwnRepo(workingDir)) ? (() => { const rt = detectRuntime(); From 5bf710b290a1fed68f9a42782b07d9b691a1681f Mon Sep 17 00:00:00 2001 From: lishuceo Date: Mon, 8 Jun 2026 19:47:40 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20manage=5Fcron=20=E6=8C=87=E5=BC=95?= =?UTF-8?q?=E9=9A=8F=20config.cron.enabled=20=E6=9D=A1=E4=BB=B6=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review 指出 cron MCP server 是条件注册的(需 CRON_ENABLED=true), 但 prompt 无条件引用 manage_cron。修复为:cron 关闭时降级为 "建议用户稍后手动追问检查结果"。 同时 export buildWorkspaceSystemPrompt 以便测试直接调用。 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/claude/executor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/claude/executor.ts b/src/claude/executor.ts index 46bc392..a958654 100644 --- a/src/claude/executor.ts +++ b/src/claude/executor.ts @@ -322,7 +322,7 @@ export function listKnownOrgs(cacheDir: string): string[] { } /** 构建工作区管理系统提示词(注入实际目录路径 + 可用项目列表) */ -function buildWorkspaceSystemPrompt(workingDir?: string, options?: { isRestart?: boolean }): string { +export function buildWorkspaceSystemPrompt(workingDir?: string, options?: { isRestart?: boolean }): string { const projectsDir = config.claude.defaultWorkDir; const cacheDir = config.repoCache.dir; const workspacesDir = config.workspace.baseDir; @@ -483,8 +483,8 @@ cd /some/other/dir && gh pr view 123 对于需要 5 分钟以上才能完成的后台任务(如跑批、大规模测试、编译): 1. 用 \`nohup ... &\` 或 \`... &\` 启动任务,确保脱离子进程 -2. 用 cron 工具(\`manage_cron\`)设一个一次性定时检查(\`schedule_kind: "at"\`),在预计完成时间触发 -3. 立即返回结果,告知用户任务已启动、预计完成时间、以及会自动回来检查 +${config.cron.enabled ? `2. 用 cron 工具(\`manage_cron\`)设一个一次性定时检查(\`schedule_kind: "at"\`),在预计完成时间触发 +3. 立即返回结果,告知用户任务已启动、预计完成时间、以及会自动回来检查` : `2. 立即返回结果,告知用户任务已启动、预计完成时间,建议用户稍后手动追问检查结果`} ### 权限交互 From 40add5430053f7c9f1b937619969357a46874fe2 Mon Sep 17 00:00:00 2001 From: lishuceo Date: Mon, 8 Jun 2026 19:47:47 +0800 Subject: [PATCH 5/5] =?UTF-8?q?test:=20=E6=B7=BB=E5=8A=A0=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E7=BA=A6=E6=9D=9F=20system=20prompt=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 验证: - system prompt 包含"平台执行约束"段落 - cron 关闭时不包含 manage_cron 指引 - cron 关闭时包含手动追问降级文案 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/claude/__tests__/executor.test.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/claude/__tests__/executor.test.ts b/src/claude/__tests__/executor.test.ts index 70f4917..9209ba5 100644 --- a/src/claude/__tests__/executor.test.ts +++ b/src/claude/__tests__/executor.test.ts @@ -69,7 +69,7 @@ vi.mock('../../cron/init.js', () => ({ getCronScheduler: vi.fn(() => null), })); -import { ClaudeExecutor } from '../executor.js'; +import { ClaudeExecutor, buildWorkspaceSystemPrompt } from '../executor.js'; // ============================================================ // Helpers @@ -581,4 +581,21 @@ describe('ClaudeExecutor', () => { expect(opts.env).toBeUndefined(); }); }); + + describe('buildWorkspaceSystemPrompt — platform constraints', () => { + it('includes platform execution constraints section', () => { + const prompt = buildWorkspaceSystemPrompt('/tmp/work'); + expect(prompt).toContain('平台执行约束'); + expect(prompt).toContain('子进程生命周期'); + expect(prompt).toContain('不要用 Monitor 等待超过 5 分钟的后台任务结果'); + expect(prompt).toContain('权限交互'); + }); + + it('omits cron guidance when cron is disabled', () => { + // config mock has cron.enabled = false + const prompt = buildWorkspaceSystemPrompt('/tmp/work'); + expect(prompt).not.toContain('manage_cron'); + expect(prompt).toContain('建议用户稍后手动追问检查结果'); + }); + }); });