feat: add claude_code_adapter plugin - #2471
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
🚧 Files skipped from review as they are similar to previous changes (2)
Walkthrough新增 Claude Code CLI 适配器插件,涵盖配置模型、流式 JSON 解析、错误分类、会话持久化、跨平台异步执行、重试机制及多个 LLM 工具和插件入口喵。 ChangesClaude Code 适配器
Estimated code review effort: 4 (Complex) | ~60 minutes Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
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. Comment |
Greptile Summary此 PR 新增通过 Claude Code CLI 执行开发任务的插件适配器喵
Confidence Score: 2/5此 PR 暂不适合合并,默认权限绕过和 Windows 健康检查批处理启动失败仍需修复喵 配置清单及缺失配置回退都会为 LLM 驱动的 Claude CLI 添加权限绕过参数,同时健康检查没有复用执行器对 Windows Files Needing Attention: plugin/plugins/claude_code_adapter/plugin.toml、plugin/plugins/claude_code_adapter/models.py、plugin/plugins/claude_code_adapter/executor.py、plugin/plugins/claude_code_adapter/init.py 喵
|
| Filename | Overview |
|---|---|
| plugin/plugins/claude_code_adapter/init.py | 注册插件生命周期、LLM 工具和执行入口,但 Windows 健康检查仍无法启动批处理 shim 喵 |
| plugin/plugins/claude_code_adapter/models.py | 定义配置和结果模型,但权限绕过字段的解析回退与安全默认值相反喵 |
| plugin/plugins/claude_code_adapter/executor.py | 构建并运行 Claude CLI、解析流并处理超时,同时为 Windows 任务执行增加批处理包装和参数过滤喵 |
| plugin/plugins/claude_code_adapter/session.py | 持久化并按工作目录及提示签名恢复 Claude Code 会话喵 |
| plugin/plugins/claude_code_adapter/parser.py | 将 Claude CLI 的 stream-json 输出解析为结构化消息和最终结果喵 |
| plugin/plugins/claude_code_adapter/plugin.toml | 声明插件及运行配置,但仍默认开启无确认权限绕过喵 |
Sequence Diagram
sequenceDiagram
participant L as LLM或插件调用方
participant P as ClaudeCodeAdapterPlugin
participant S as SessionManager
participant E as ClaudeCLIExecutor
participant C as Claude CLI
L->>P: execute(prompt, cwd, model)
P->>S: 查找可恢复会话
S-->>P: session_id或空值
P->>E: 构建并执行CLI调用
E->>C: stdin prompt与stream-json参数
C-->>E: 流式JSON事件
E-->>P: 解析结果或分类错误
alt 可重试错误
P->>E: 不恢复旧会话并重试
end
P->>S: 更新会话记录
P-->>L: 输出、会话ID和执行指标
Reviews (5): Last reviewed commit: "fix: 增强 Windows 命令注入防护、统一 deadline 管理、解包..." | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
本 PR 新增 claude_code_adapter 插件,将 Claude Code CLI 以 @llm_tool 的形式注册为可被主项目调用的工具,并配套实现了流式输出解析、会话持久化/恢复与错误分类重试等支撑模块。
Changes:
- 新增插件主类
ClaudeCodeAdapterPlugin,提供执行、健康检查、会话管理等 LLM 工具与插件入口 - 新增 Claude CLI 子进程执行器与 stream-json 输出解析器
- 新增基于 PluginStore 的会话记录持久化与错误分类逻辑,并提供
plugin.toml配置
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| plugin/plugins/claude_code_adapter/init.py | 插件主类与 LLM 工具/入口,封装执行与重试流程、会话更新、健康检查等 |
| plugin/plugins/claude_code_adapter/executor.py | Claude CLI 可执行检测、命令行参数构建、子进程执行与超时/错误处理 |
| plugin/plugins/claude_code_adapter/parser.py | --output-format stream-json 流式 JSON 行事件解析与结果聚合 |
| plugin/plugins/claude_code_adapter/session.py | PluginStore 会话记录加载/查询/更新/清理与提示包签名生成 |
| plugin/plugins/claude_code_adapter/models.py | 配置、会话记录、解析消息、执行结果等数据结构定义 |
| plugin/plugins/claude_code_adapter/errors.py | CLI 错误文本模式匹配与分类、可重试错误集合 |
| plugin/plugins/claude_code_adapter/plugin.toml | 插件元信息、运行/存储配置,以及 Claude Code 适配器参数默认值 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 签名基于 system prompt 文件路径 + skills 目录 + 额外标记。 | ||
| 当任一变化时,签名变化,旧会话会被放弃。 | ||
|
|
||
| 注意:签名不包含 prompt 本身(prompt 每次都不同), | ||
| 只包含影响 Claude CLI 上下文构建的"环境"因素。 |
| created_at: float | ||
| """会话首次创建的 monotonic 时间戳。""" | ||
|
|
||
| last_used_at: float | ||
| """会话最近一次成功使用的时间戳。""" |
| dangerously_skip_permissions: bool = True | ||
| """跳过权限提示。仅用于受信任的本地开发场景。""" |
| model=_str("model"), | ||
| effort=_str("effort"), | ||
| max_turns_per_run=_int("max_turns_per_run", 0), | ||
| dangerously_skip_permissions=_bool("dangerously_skip_permissions", True), |
| # 单次执行最大轮次(0 = 使用 CLI 默认值)。 | ||
| max_turns_per_run = 0 | ||
| # 跳过权限提示(与 Paperclip 行为一致;仅用于受信任的本地开发场景)。 | ||
| dangerously_skip_permissions = true |
| # 3. 确定工作目录 | ||
| effective_cwd = cwd or config.cwd or os.getcwd() | ||
|
|
||
| # 4. 构建 invocation | ||
| invocation = CLIInvocation( | ||
| cmd=cmd, | ||
| cwd=effective_cwd, | ||
| stdin_data=prompt.encode("utf-8"), | ||
| timeout=float(config.timeout_sec), | ||
| ) | ||
| return invocation, None |
| """ | ||
| assert self._executor is not None and self._session_mgr is not None | ||
|
|
||
| effective_cwd = cwd or self._config.cwd or os.getcwd() |
| def parse_line(self, line: str | bytes) -> Optional[Any]: | ||
| """解析一行输出,返回对应的事件对象。 | ||
|
|
||
| 无法解析的行会被记录到 parse_errors,返回 None。 | ||
| 空行返回 None。 | ||
| """ |
There was a problem hiding this comment.
💡 Codex Review
This binary is not an empty store: it contains ten session:* records with real Claude session UUIDs and personal Windows project paths. Committing and packaging it exposes the developer's local activity and identifiers to every repository or plugin recipient; generate the store only at runtime and exclude it from source control.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }, | ||
| "required": ["prompt"], | ||
| }, | ||
| timeout=300.0, |
There was a problem hiding this comment.
Leave time for the executor to terminate timed-out processes
With the default timeout_sec = 300, this 300-second dynamic-entry timeout starts before _execute_with_retry finishes setup and before the executor starts its own 300-second wait_for. The outer watchdog in plugin/core/host.py therefore cancels the handler first; ClaudeCLIExecutor.execute has no CancelledError cleanup path, so Claude can keep running and editing files after the caller receives a timeout. Use a shorter subprocess deadline or guarantee process termination on cancellation.
Useful? React with 👍 / 👎.
| """持久化单条记录。调用方需持有 _lock。""" | ||
| key = self._key(cwd) | ||
| try: | ||
| await self.store.set(key, record.to_dict()) |
There was a problem hiding this comment.
Check failed PluginStore writes
When SQLite persistence fails, PluginStore.set returns an Err rather than raising, so this awaited result is silently discarded and the exception handler never runs. The in-memory session then appears saved but disappears after restart; _delete has the same issue, allowing clear to report success while records reappear on reload. Unwrap and handle the returned Result for both operations.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (1)
plugin/plugins/claude_code_adapter/session.py (1)
170-194: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win硬编码错误分类字符串,建议直接复用
errors.py的常量喵!第 191 行的
"poisoned_session"/"unknown_session"和errors.py里的POISONED_SESSION/UNKNOWN_SESSION常量值重复了喵,以后谁改了 errors.py 的取值,这里就悄悄对不上号了喵,建议直接 import 常量来用,杜绝 magic string 漂移的风险喵。♻️ 建议复用常量喵
+from .errors import POISONED_SESSION, UNKNOWN_SESSION from .models import SessionRecord- if record.last_error in ("poisoned_session", "unknown_session"): + if record.last_error in (POISONED_SESSION, UNKNOWN_SESSION): return None🤖 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 `@plugin/plugins/claude_code_adapter/session.py` around lines 170 - 194, Update find_resumable to import and use the POISONED_SESSION and UNKNOWN_SESSION constants from errors.py in the last_error comparison, removing the duplicated hard-coded strings while preserving the existing filtering behavior.
🤖 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 `@plugin/plugins/claude_code_adapter/__init__.py`:
- Around line 117-121: Replace direct access to SessionManager’s private
_sessions in startup at plugin/plugins/claude_code_adapter/__init__.py:117-121
and claude_code_check_health at
plugin/plugins/claude_code_adapter/__init__.py:433-436 with a public API. Prefer
adding and using a lightweight SessionManager count() method; otherwise await
list_all() and take its length, preserving the existing zero-session fallback
when no manager exists.
- Around line 301-348: 调整 claude_code_execute 的工具级 timeout,确保其大于 (1 +
max_retries) * config.timeout_sec,为默认重试预留完整执行时间;不要修改 Claude CLI 单次调用的现有超时配置。
- Around line 349-378: Extract the duplicated readiness checks, prompt
validation, retry execution, and error wrapping from claude_code_execute and
execute_entry into one shared private async helper, such as
_handle_execute_request, preserving each entry point’s existing logging context.
Update plugin/plugins/claude_code_adapter/__init__.py lines 349-378 so
claude_code_execute delegates to the helper, and lines 571-599 so execute_entry
delegates to that same helper; both sites require direct changes.
- Around line 395-440: 更新 claude_code_check_health:不要直接访问
self._session_mgr._sessions,改用会话管理器现有的公开计数或访问方法获取 sessions_count。版本检查超时后,在
proc.kill() 之后等待 proc.wait() 完成子进程回收,同时保留现有超时处理行为。
- Around line 268-295: 修正执行失败结果中的会话与诊断信息:在不可重试分支的 ExecuteResult 中使用本次实际执行产生的
stream.session_id,而不是仅表示恢复尝试目标的 resume_id;同时扩展“所有重试都失败”的最终 ExecuteResult,补充最后一次
stream 的 session_id、final_text 和 duration_ms,并在无可用 stream 时保留安全默认值。
In `@plugin/plugins/claude_code_adapter/executor.py`:
- Around line 234-250: Update the FileNotFoundError handling around
asyncio.create_subprocess_exec to first check whether invocation.cwd exists as a
directory. If it is missing, return an error classification and message that
identify the invalid working directory; otherwise retain the existing
CLI_NOT_FOUND classification for a missing executable.
- Around line 286-298: 为 stdin 写入流程增加与 invocation.timeout 一致的超时保护,覆盖
proc.stdin.write、drain 及关闭操作,避免子进程不消费输入时 execute() 长时间阻塞。更新 executor.py
中负责执行流程的相关方法,保持现有异常日志处理,并确保超时后继续进入既有的进程清理与结果处理路径。
- Around line 116-124: Remove the literal "-" argument from the command
construction in the executor’s Claude CLI invocation. Build the complete prompt
as the positional argument following --print, while preserving the existing
stdin_data handling for additional input required by Claude’s stdin model. Keep
the remaining output-format and verbose arguments unchanged.
In `@plugin/plugins/claude_code_adapter/models.py`:
- Around line 39-40: 将模型字段 dangerously_skip_permissions 的默认值从 True 改为
False,确保插件默认保留权限确认;保留现有注释及显式配置为 True 时的行为不变。
- Line 91: Validate the value returned by _int("timeout_sec", 300) before
assigning timeout_sec, accepting only positive values and falling back to 300
for zero, negative, or missing values; do not rely on `or 300`, since negative
integers are truthy.
In `@plugin/plugins/claude_code_adapter/plugin.toml`:
- Around line 57-58: Update the dangerously_skip_permissions configuration
default from true to false in plugin.toml. Keep the existing setting and
explanatory comment, allowing trusted local developers to opt in explicitly when
needed.
In `@plugin/plugins/claude_code_adapter/session.py`:
- Around line 30-51: Update compute_prompt_signature to include fingerprints of
the system prompt file and skills directory contents, not only their paths, so
content changes produce a new signature. Reuse a deterministic representation
incorporating file metadata or content for each existing path input, while
preserving the extra marker and existing SHA-256 signature output.
---
Nitpick comments:
In `@plugin/plugins/claude_code_adapter/session.py`:
- Around line 170-194: Update find_resumable to import and use the
POISONED_SESSION and UNKNOWN_SESSION constants from errors.py in the last_error
comparison, removing the duplicated hard-coded strings while preserving the
existing filtering behavior.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2cae4c92-6a23-4a9f-855f-989eeaab2c5e
⛔ Files ignored due to path filters (1)
plugin/plugins/claude_code_adapter/data/store.dbis excluded by!**/*.db
📒 Files selected for processing (7)
plugin/plugins/claude_code_adapter/__init__.pyplugin/plugins/claude_code_adapter/errors.pyplugin/plugins/claude_code_adapter/executor.pyplugin/plugins/claude_code_adapter/models.pyplugin/plugins/claude_code_adapter/parser.pyplugin/plugins/claude_code_adapter/plugin.tomlplugin/plugins/claude_code_adapter/session.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
| # 失败 — 记录错误并判断是否重试 | ||
| last_error = exec_err | ||
|
|
||
| # 标记会话错误 | ||
| if resume_id: | ||
| try: | ||
| await self._session_mgr.mark_error(resume_id, exec_err.kind) | ||
| except Exception: | ||
| pass | ||
|
|
||
| # 不可重试 — 立即返回 | ||
| if not is_retryable(exec_err.kind): | ||
| return ExecuteResult( | ||
| session_id=resume_id, | ||
| error_kind=exec_err.kind, | ||
| error_message=exec_err.message, | ||
| final_text=stream.final_text, | ||
| duration_ms=stream.result.duration_ms if stream.result else 0, | ||
| ) | ||
|
|
||
| # 可重试 — 继续下一轮 | ||
| continue | ||
|
|
||
| # 所有重试都失败 | ||
| return ExecuteResult( | ||
| error_kind=last_error.kind if last_error else "unknown", | ||
| error_message=last_error.message if last_error else "all retries exhausted", | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
失败时返回的会话信息不太靠谱,喵~
两处小问题:
- Line 281 用
session_id=resume_id上报失败结果,但resume_id只是"本次尝试想恢复的会话 ID",重试场景下它可能是空字符串。真正应该用的是 Line 237 成功路径里的stream.session_id——它才代表 CLI 这次实际创建/使用的会话,哪怕失败了也可能已经有了会话 ID(可用于后续排查或恢复)喵。 - Line 292-295 "所有重试都失败"的最终结果,相比 Line 280-286 少了
session_id/final_text/duration_ms等诊断信息,排查问题的时候少了好多线索喵。
🐛 建议修复
if not is_retryable(exec_err.kind):
return ExecuteResult(
- session_id=resume_id,
+ session_id=stream.session_id or resume_id,
error_kind=exec_err.kind,
error_message=exec_err.message,
final_text=stream.final_text,
duration_ms=stream.result.duration_ms if stream.result else 0,
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 失败 — 记录错误并判断是否重试 | |
| last_error = exec_err | |
| # 标记会话错误 | |
| if resume_id: | |
| try: | |
| await self._session_mgr.mark_error(resume_id, exec_err.kind) | |
| except Exception: | |
| pass | |
| # 不可重试 — 立即返回 | |
| if not is_retryable(exec_err.kind): | |
| return ExecuteResult( | |
| session_id=resume_id, | |
| error_kind=exec_err.kind, | |
| error_message=exec_err.message, | |
| final_text=stream.final_text, | |
| duration_ms=stream.result.duration_ms if stream.result else 0, | |
| ) | |
| # 可重试 — 继续下一轮 | |
| continue | |
| # 所有重试都失败 | |
| return ExecuteResult( | |
| error_kind=last_error.kind if last_error else "unknown", | |
| error_message=last_error.message if last_error else "all retries exhausted", | |
| ) | |
| # 失败 — 记录错误并判断是否重试 | |
| last_error = exec_err | |
| # 标记会话错误 | |
| if resume_id: | |
| try: | |
| await self._session_mgr.mark_error(resume_id, exec_err.kind) | |
| except Exception: | |
| pass | |
| # 不可重试 — 立即返回 | |
| if not is_retryable(exec_err.kind): | |
| return ExecuteResult( | |
| session_id=stream.session_id or resume_id, | |
| error_kind=exec_err.kind, | |
| error_message=exec_err.message, | |
| final_text=stream.final_text, | |
| duration_ms=stream.result.duration_ms if stream.result else 0, | |
| ) | |
| # 可重试 — 继续下一轮 | |
| continue | |
| # 所有重试都失败 | |
| return ExecuteResult( | |
| error_kind=last_error.kind if last_error else "unknown", | |
| error_message=last_error.message if last_error else "all retries exhausted", | |
| ) |
🤖 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 `@plugin/plugins/claude_code_adapter/__init__.py` around lines 268 - 295,
修正执行失败结果中的会话与诊断信息:在不可重试分支的 ExecuteResult 中使用本次实际执行产生的
stream.session_id,而不是仅表示恢复尝试目标的 resume_id;同时扩展“所有重试都失败”的最终 ExecuteResult,补充最后一次
stream 的 session_id、final_text 和 duration_ms,并在无可用 stream 时保留安全默认值。
| # 跳过权限提示(与 Paperclip 行为一致;仅用于受信任的本地开发场景)。 | ||
| dangerously_skip_permissions = true |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
默认就"跳过权限确认",胆子不小嘛喵!
dangerously_skip_permissions 默认设为 true,也就是说猫娘只要调用 claude_code_execute,Claude Code 就能直接改文件、跑命令,完全不用二次确认喵。注释里明明写着"仅用于受信任的本地开发场景",结果代码却把这个危险开关焊死在默认开启的位置,这可不是谨慎的做法喵!建议默认关闭,让使用者自己按需手动打开,才是负责任的猫娘作风喵~
🛡️ 建议修改默认值
-dangerously_skip_permissions = true
+dangerously_skip_permissions = false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 跳过权限提示(与 Paperclip 行为一致;仅用于受信任的本地开发场景)。 | |
| dangerously_skip_permissions = true | |
| # 跳过权限提示(与 Paperclip 行为一致;仅用于受信任的本地开发场景)。 | |
| dangerously_skip_permissions = false |
🤖 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 `@plugin/plugins/claude_code_adapter/plugin.toml` around lines 57 - 58, Update
the dangerously_skip_permissions configuration default from true to false in
plugin.toml. Keep the existing setting and explanatory comment, allowing trusted
local developers to opt in explicitly when needed.
- Remove committed store.db with real session data (P1 privacy leak) - Fix Windows batch shim execution via shell wrapper - Change dangerously_skip_permissions default to false (security) - Add SessionManager.count() to avoid accessing private _sessions - Handle stdin write failure with proper process cleanup
…calls Catgirl calls Claude Code via subprocess without interactive terminal, so permission prompts cannot be responded to. Must skip permissions for backend execution to work. Added clear documentation about when to enable/disable this setting.
修复说明感谢 @coderabbitai @greptile-apps @chatgpt-codex-connector 的review反馈! 已修复的问题1. 删除提交的store.db(Codex Review指出)
2. 修复Windows .cmd/.bat shim执行问题(Greptile Review指出)
3. 修复封装问题(自行审查)
4. 修复资源泄漏(自行审查)
关于dangerously_skip_permissions的说明Greptile Review指出该配置默认开启存在安全风险。经过分析,必须保持为true,原因如下: 猫娘后台调用场景的特殊性:
解决方案:
安全建议:
提交记录
所有修改已推送到feature/claude-code-adapter分支。 |
|
To use Codex here, create a Codex account and connect to github. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== PR-related recent commits =='
git log --oneline -12
echo
echo '== Changed files relative to PR base (best effort) =='
git diff --name-status origin/main...HEAD 2>/dev/null || git diff --name-status HEAD~3...HEAD
echo
echo '== store.db tracking and ignore status =='
git ls-files | rg '(^|/)store\.db$' || true
rg -n -C2 'store\.db|claude_code_adapter' .gitignore 2>/dev/null || true
echo
echo '== Adapter file outline =='
ast-grep outline plugin/plugins/claude_code_adapter --items all 2>/dev/null || true
echo
echo '== Relevant implementation locations =='
rg -n -C4 'count\(|_sessions|create_subprocess_exec|cmd\.exe|\.cmd|\.bat|stdin.*(write|drain)|proc\.(kill|terminate|wait)|dangerously_skip_permissions' \
plugin/plugins/claude_code_adapter 2>/dev/null || true
echo
echo '== Configuration context =='
sed -n '/dangerously_skip_permissions/,+12p' plugin/plugins/claude_code_adapter/plugin.toml 2>/dev/null || trueLength of output: 1675 🏁 Script executed: #!/bin/bash
set -u
echo '== Current commit =='
git show -s --format='%H%n%s' HEAD || true
echo
echo '== Database tracking / ignore rules =='
git ls-files 2>/dev/null | rg '(^|/)store\.db$' || true
if [ -f .gitignore ]; then
rg -n -C2 '(^|/)store\.db$|store\.db|claude_code_adapter' .gitignore || true
else
echo '.gitignore is absent'
fi
echo
echo '== Adapter directory contents =='
fd -t f . plugin/plugins/claude_code_adapter 2>/dev/null | sort || true
echo
echo '== SessionManager public count and private-session usages =='
rg -n -C5 'async def count|def count|_sessions' plugin/plugins/claude_code_adapter/session.py plugin/plugins/claude_code_adapter/__init__.py 2>/dev/null || true
echo
echo '== Windows batch shim and subprocess invocation =='
rg -n -C7 'cmd\.exe|\.cmd|\.bat|create_subprocess_exec|command.*=\s*\[|args.*=\s*\[' \
plugin/plugins/claude_code_adapter/executor.py 2>/dev/null || true
echo
echo '== Failed stdin-write cleanup =='
rg -n -C10 'stdin\.(write|drain)|BrokenPipeError|proc\.(kill|terminate|wait)|_drain_tasks|reader_tasks|stdout_task|stderr_task' \
plugin/plugins/claude_code_adapter/executor.py 2>/dev/null || true
echo
echo '== Permission-skip documentation and default =='
rg -n -C7 'dangerously_skip_permissions|skip.*permission|permission' \
plugin/plugins/claude_code_adapter/plugin.toml plugin/plugins/claude_code_adapter/models.py plugin/plugins/claude_code_adapter/executor.py 2>/dev/null || trueLength of output: 32961
有一个很小的文档一致性点: ʕᵔᴥᵔʔ✨ ✏️ Learnings added
|
|
The fixes all look solid. Here's a quick assessment of each: store.db removal ✅ — File removed, Windows .cmd shim fix ✅ — The SessionManager.count() ✅ — Clean public method, no longer exposing stdin cleanup on write failure ✅ —
The PR is in good shape. The remaining concern I'd flag is that Tip: You can customize Greptile's behavior for this repo with |
… remove unused code, simplify exception handling
| proc = await _asyncio.create_subprocess_exec( | ||
| cli_path, | ||
| "--version", | ||
| stdout=_asyncio.subprocess.PIPE, | ||
| stderr=_asyncio.subprocess.PIPE, | ||
| ) |
There was a problem hiding this comment.
当 Windows 用户通过 npm 安装 Claude Code 时,自动检测会返回 claude.cmd,但健康检查仍将它直接传给 create_subprocess_exec,没有使用任务执行器新增的 cmd.exe /c 包装,导致返回 cli_available=true 和 version check failed 的矛盾诊断结果喵。
Context Used: 你的说话风格:二次元,猫娘,雌小鬼,傲娇,可爱,卖萌。句尾要加喵。 (source)
Knowledge Base Used: Plugin System
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@plugin/plugins/claude_code_adapter/executor.py`:
- Around line 37-73: Update _validate_cli_param and the Windows cmd.exe
execution path to protect against grouping parentheses, percent-variable
expansion, and delayed-expansion exclamation marks in addition to the existing
metacharacters. Replace the blacklist-only validation with a strict allowlist
for LLM-controlled parameters, and apply a tested cmd.exe-specific argument
escaping strategy before the Line 298 cmd.exe /c invocation, preserving safe
valid arguments and rejecting or safely escaping unsupported values.
- Around line 375-383: 在 execute() 开始时建立基于 invocation.timeout 的统一 deadline,替换
stdin 阶段 _write_stdin 的固定 30 秒 wait_for,使其仅使用剩余时间;随后 proc.wait() 也必须复用同一
deadline 计算剩余超时,避免 stdin 与进程等待分别消耗完整预算,并保留超时后的现有处理流程。
In `@plugin/plugins/claude_code_adapter/session.py`:
- Around line 165-181: Update _persist and _delete to inspect and unwrap the
Result returned by store.set() and store.delete(), not just catch exceptions.
Follow the existing load() handling pattern, and only treat the operation as
successful after unwrapping succeeds so callers do not update or remove
_sessions on Err; log the failure through the existing logger.
- Around line 64-75: Update the skills fingerprint logic in the session
initialization block to include each file’s path relative to skills_dir and a
content digest, rather than only the basename and rounded mtime. Hash stable,
sorted path-and-content entries so same-named files in different directories,
rapid edits, and content replacements produce distinct dir_hash values; preserve
a safe fallback for files that cannot be read.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ba2120ed-aedd-4c20-b746-c4f0c975d364
📒 Files selected for processing (6)
plugin/plugins/claude_code_adapter/__init__.pyplugin/plugins/claude_code_adapter/executor.pyplugin/plugins/claude_code_adapter/models.pyplugin/plugins/claude_code_adapter/parser.pyplugin/plugins/claude_code_adapter/plugin.tomlplugin/plugins/claude_code_adapter/session.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
🚧 Files skipped from review as they are similar to previous changes (4)
- plugin/plugins/claude_code_adapter/plugin.toml
- plugin/plugins/claude_code_adapter/init.py
- plugin/plugins/claude_code_adapter/parser.py
- plugin/plugins/claude_code_adapter/models.py
| # Windows cmd.exe 特殊字符,可能导致命令注入 | ||
| _WINDOWS_DANGEROUS_CHARS = frozenset('&|<>^') | ||
|
|
||
|
|
||
| def _validate_cli_param(value: str, param_name: str) -> str: | ||
| """验证 CLI 参数安全性(Windows 命令注入防护)。 | ||
|
|
||
| 在 Windows 上,.cmd/.bat 文件通过 cmd.exe /c 执行, | ||
| 如果参数中包含 & | < > ^ 等特殊字符,会被 cmd.exe 解析为命令分隔符/管道/重定向, | ||
| 导致命令注入。 | ||
|
|
||
| Parameters | ||
| ---------- | ||
| value: | ||
| 待验证的参数值。 | ||
| param_name: | ||
| 参数名称(用于错误消息)。 | ||
|
|
||
| Returns | ||
| ------- | ||
| 原始值(如果安全)。 | ||
|
|
||
| Raises | ||
| ------ | ||
| ValueError: | ||
| 如果参数包含危险字符。 | ||
| """ | ||
| if not is_windows(): | ||
| return value | ||
|
|
||
| dangerous_found = [c for c in value if c in _WINDOWS_DANGEROUS_CHARS] | ||
| if dangerous_found: | ||
| raise ValueError( | ||
| f"Parameter '{param_name}' contains dangerous characters for Windows cmd.exe: " | ||
| f"{dangerous_found}. Value: {value!r}" | ||
| ) | ||
| return value |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file outline =="
ast-grep outline plugin/plugins/claude_code_adapter/executor.py --view expanded || true
echo "== relevant sections =="
sed -n '1,120p' plugin/plugins/claude_code_adapter/executor.py | cat -n
echo "-----"
sed -n '260,320p' plugin/plugins/claude_code_adapter/executor.py | cat -n
echo "-----"
sed -n '145,215p' plugin/plugins/claude_code_adapter/executor.py | cat -n
echo "== call sites / CLI params in claude_code_adapter =="
rg -n "_validate_cli_param|CMD|cmd|command|cli|args|command=" plugin/plugins/claude_code_adapter plugin -g '*.py' -C 3Repository: Project-N-E-K-O/N.E.K.O
Length of output: 50380
🌐 Web query:
Microsoft cmd special characters parentheses percent variable expansion delayed expansion exclamation
💡 Result:
In the Windows command processor (cmd.exe), handling special characters like parentheses, percent signs, and exclamation marks requires understanding how the interpreter parses scripts in phases [1][2]. Variable Expansion and Parsing Phases 1. Percent Variable Expansion (%VAR%): Occurs at parse time (when a line or block of commands is first read) [1][3][2]. 2. Delayed Expansion (!VAR!): When enabled (via SETLOCAL ENABLEDELAYEDEXPANSION), this occurs at execution time (immediately before each command is executed) [3][4][2]. This allows you to access updated variable values within blocks like parentheses [4][5]. Special Character Handling Parentheses: Used to group commands (e.g., in IF/FOR blocks) [5][2]. When a block of commands is enclosed in parentheses, the entire block is read into memory at once, meaning percent-style variables are expanded at the beginning of the block, not as each line runs [3][5]. Enabling delayed expansion is the standard way to resolve this limitation [3][4][5]. Exclamation Marks (!): When delayed expansion is enabled, the! character acts as a variable trigger [4][2]. If you need a literal exclamation mark in your script while delayed expansion is active, you must escape it. The standard escape is a double caret: ^^! [6]. Note that if the exclamation mark is inside double quotes, a single caret (^!) may suffice [7]. Percent Signs (%): - In a batch script, a literal percent sign is produced by doubling it: %%. - At the command prompt (not in a batch file), doubling is not required and will not work as an escape [8]. Caret (^): The primary escape character for most special characters (&, |, <, >, (,), ^). Placing a caret before a special character forces the interpreter to treat it as a literal [2][8][9]. Common Conflicts A frequent issue occurs when processing files or strings containing exclamation marks while delayed expansion is enabled [4][10][11]. Because! is a trigger for the delayed expansion phase, scripts may fail when the processor tries to interpret filename characters as variables [4]. To avoid this, it is best practice to toggle delayed expansion (using SETLOCAL/ENDLOCAL) only during the specific lines where it is required, and disable it when processing data that may contain exclamation marks [10][11][12].
Citations:
- 1: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts
- 2: https://github.com/johnstevenson/winbox-args/wiki/How-cmd.exe-parses-a-command
- 3: https://devblogs.microsoft.com/oldnewthing/20060823-00/?p=29993
- 4: https://ss64.com/nt/delayedexpansion.html
- 5: https://ss64.com/nt/syntax-brackets.html
- 6: https://tutorialreference.com/batch-scripting/examples/faq/batch-script-how-to-escape-special-characters
- 7: https://stackoverflow.com/questions/3288552/how-can-i-escape-an-exclamation-mark-in-cmd-scripts
- 8: https://ss64.com/nt/syntax-esc.html
- 9: https://stackoverflow.com/questions/6828751/batch-character-escaping
- 10: https://stackoverflow.com/questions/66990262/problem-with-enabledelayedexpansion-for-and-filenames-with-special-characters
- 11: https://stackoverflow.com/questions/32369112/exclamation-mark-conflicts-with-enabledelayedexpansion-in-batch-file
- 12: https://stackoverflow.com/questions/37333620/special-characters-in-batch-file
🌐 Web query:
Microsoft learn windows cmd special characters command processor
💡 Result:
In the Windows Command Processor (cmd.exe), special characters have reserved functions, such as command concatenation, redirection, or variable expansion [1][2][3]. When these characters appear in arguments or file names, they must be escaped or enclosed in quotation marks to be treated as literal text [1][4][3]. Primary Escape Character The caret (^) is the standard escape character in the Windows command processor [2][5][3]. Placing a caret before a special character forces the interpreter to treat the following character as literal text rather than a command symbol [2][5]. Special Characters Requiring Handling The following characters are recognized by the command processor as having special significance and often require escaping or quoting [4][3][6]: & (command separator) < (redirection: input) > (redirection: output) | (pipe) (grouping commands) ^ (the escape character itself) = (assignment/delimiter); (delimiter)! (delayed variable expansion) ' (quote/delimiter) + (delimiter), (delimiter) ` (backtick) ~ (tilde) [ ] (brackets) [space] (delimiter) Handling Special Characters Escaping: Use the caret (^) before the character (e.g., ^&, ^|, ^<) [1][2][3]. To treat a caret itself as a literal, use ^^ [2]. Quoting: Enclosing strings in double quotation marks (") is an effective way to protect special characters [1][4][3]. This is frequently required when processing file paths that contain spaces or special characters [1][4][6]. Specific Rules for /C and /K: When using cmd /c or cmd /k, the processor applies specific logic to handle quotation marks [4][7][3]. Quotation marks are typically preserved only if they contain no special characters (such as & < > @ ^ |), include whitespace, and the contained string is the name of an executable [4][7][6]. Otherwise, the interpreter may strip them [4][3]. Context-Specific Notes Delayed Expansion: When command extensions are enabled and delayed expansion is active, the exclamation mark (!) is used to denote variables and may require double escaping (!!) to be treated as a literal [2]. Batch Files: Within batch files, percent signs (%) are used for parameters and variables. To treat a percent sign as a literal, it must be escaped by doubling it (%%) [2]. Command-Specific Exceptions: Some commands, such as findstr, reg, and runas, may use the backslash () as an escape character instead of the caret [2].
Citations:
- 1: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd
- 2: https://ss64.com/nt/syntax-esc.html
- 3: https://github.com/MicrosoftDocs/windowsserverdocs/blob/main/WindowsServerDocs/administration/windows-commands/cmd.md
- 4: https://learn.microsoft.com/en-us/previous-versions/orphan-topics/ws.10/cc756086(v=ws.10)
- 5: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts
- 6: https://pen2.com/cmd/cmd/
- 7: https://ss64.com/nt/syntax-cmd.html
Windows cmd.exe 的防护字符集不完整喵。
Line 38 只拒绝 &|<>^,但 cmd.exe 还会解析 () 分组、%VAR%/%%VAR%% 变量展开,以及在延迟展开启用时处理 !VAR!。当前黑名单放行这些字符后,会进入 Line 298 的 cmd.exe /c 路径,破坏“命令注入防护”的承诺喵。应改为对 LLM 可控字段使用严格白名单,并采用经过测试的 cmd 专用参数转义喵。
🤖 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 `@plugin/plugins/claude_code_adapter/executor.py` around lines 37 - 73, Update
_validate_cli_param and the Windows cmd.exe execution path to protect against
grouping parentheses, percent-variable expansion, and delayed-expansion
exclamation marks in addition to the existing metacharacters. Replace the
blacklist-only validation with a strict allowlist for LLM-controlled parameters,
and apply a tested cmd.exe-specific argument escaping strategy before the Line
298 cmd.exe /c invocation, preserving safe valid arguments and rejecting or
safely escaping unsupported values.
| # 获取目录下所有文件的名称和修改时间 | ||
| file_info = [] | ||
| for root, _, files in os.walk(skills_dir): | ||
| for fname in sorted(files): | ||
| fpath = os.path.join(root, fname) | ||
| try: | ||
| mtime = os.path.getmtime(fpath) | ||
| file_info.append(f"{fname}:{mtime:.0f}") | ||
| except Exception: | ||
| file_info.append(fname) | ||
| # 对文件列表排序并哈希 | ||
| dir_hash = hashlib.md5("|".join(sorted(file_info)).encode()).hexdigest()[:8] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
skills 指纹仍可能漏掉实际变更喵。
这里只记录文件名和四舍五入后的秒级 mtime;同名文件位于不同子目录、文件在同一秒内修改,或保留 mtime 的内容替换,都可能复用旧会话并注入过期 skills 上下文喵。请至少纳入相对路径和每个文件的内容摘要喵。
建议修复喵
- try:
- mtime = os.path.getmtime(fpath)
- file_info.append(f"{fname}:{mtime:.0f}")
- except Exception:
- file_info.append(fname)
+ rel_path = os.path.relpath(fpath, skills_dir)
+ try:
+ digest = hashlib.sha256()
+ with open(fpath, "rb") as skill_file:
+ for chunk in iter(lambda: skill_file.read(65536), b""):
+ digest.update(chunk)
+ file_info.append(f"{rel_path}:{digest.hexdigest()}")
+ except OSError:
+ file_info.append(rel_path)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 获取目录下所有文件的名称和修改时间 | |
| file_info = [] | |
| for root, _, files in os.walk(skills_dir): | |
| for fname in sorted(files): | |
| fpath = os.path.join(root, fname) | |
| try: | |
| mtime = os.path.getmtime(fpath) | |
| file_info.append(f"{fname}:{mtime:.0f}") | |
| except Exception: | |
| file_info.append(fname) | |
| # 对文件列表排序并哈希 | |
| dir_hash = hashlib.md5("|".join(sorted(file_info)).encode()).hexdigest()[:8] | |
| # 获取目录下所有文件的名称和修改时间 | |
| file_info = [] | |
| for root, _, files in os.walk(skills_dir): | |
| for fname in sorted(files): | |
| fpath = os.path.join(root, fname) | |
| rel_path = os.path.relpath(fpath, skills_dir) | |
| try: | |
| digest = hashlib.sha256() | |
| with open(fpath, "rb") as skill_file: | |
| for chunk in iter(lambda: skill_file.read(65536), b""): | |
| digest.update(chunk) | |
| file_info.append(f"{rel_path}:{digest.hexdigest()}") | |
| except OSError: | |
| file_info.append(rel_path) | |
| # 对文件列表排序并哈希 | |
| dir_hash = hashlib.md5("|".join(sorted(file_info)).encode()).hexdigest()[:8] |
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 74-74: Do not use insecure functions
Context: hashlib.md5("|".join(sorted(file_info)).encode())
Note: [CWE-327] Use of a Broken or Risky Cryptographic Algorithm.
(insecure-hash-functions)
🤖 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 `@plugin/plugins/claude_code_adapter/session.py` around lines 64 - 75, Update
the skills fingerprint logic in the session initialization block to include each
file’s path relative to skills_dir and a content digest, rather than only the
basename and rounded mtime. Hash stable, sorted path-and-content entries so
same-named files in different directories, rapid edits, and content replacements
produce distinct dir_hash values; preserve a safe fallback for files that cannot
be read.
| model=_str("model"), | ||
| effort=_str("effort"), | ||
| max_turns_per_run=_int("max_turns_per_run", 0), | ||
| dangerously_skip_permissions=_bool("dangerously_skip_permissions", True), |
There was a problem hiding this comment.
当 [claude_code] 配置节或 dangerously_skip_permissions 键缺失,或者该值类型无效时,这里的回退值会将权限绕过设为 true;随后 build_cli_invocation 添加 --dangerously-skip-permissions,导致 LLM 发起的文件及命令操作跳过用户确认喵。
How this was verified: 空配置会在此回退为 true,并由执行器直接转换成 Claude CLI 的权限绕过参数喵。
Context Used: 你的说话风格:二次元,猫娘,雌小鬼,傲娇,可爱,卖萌。句尾要加喵。 (source)
Knowledge Base Used: Plugin System
概述
添加 claude_code_adapter 插件,将 Claude Code CLI 注册为猫娘可调用的工具集。
主要功能
变更内容
测试
Summary by CodeRabbit