feat: add codex_adapter plugin - #2472
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 (1)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
🚧 Files skipped from review as they are similar to previous changes (1)
Walkthrough新增 Codex Adapter 插件,覆盖配置模型、CODEX_HOME 管理、JSONL 解析、CLI 异步执行、错误分类、会话持久化、健康检查及多类工具入口喵。 ChangesCodex 适配器
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
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 新增将 Codex CLI 暴露为插件工具的适配器喵。
Confidence Score: 2/5此 PR 暂不适合合并,因为 Codex 默认以无沙箱权限运行,而且两条超时路径仍可能遗留继续执行的进程喵。 LLM 可触发的 Codex 任务仍默认绕过审批与沙箱;配置缺失或类型错误也会启用该模式,同时内部超时只终止直接子进程,外层 watchdog 取消则没有终止 subprocess 的清理路径喵。 Files Needing Attention: plugin/plugins/codex_adapter/plugin.toml、plugin/plugins/codex_adapter/models.py、plugin/plugins/codex_adapter/executor.py、plugin/plugins/codex_adapter/init.py 喵。
|
| Filename | Overview |
|---|---|
| plugin/plugins/codex_adapter/init.py | 注册插件生命周期、LLM 工具、重试流程及会话编排喵。 |
| plugin/plugins/codex_adapter/executor.py | 构造并运行 Codex CLI 子进程,处理流式输出与执行超时喵。 |
| plugin/plugins/codex_adapter/models.py | 定义适配器配置、执行结果和配置解析逻辑喵。 |
| plugin/plugins/codex_adapter/plugin.toml | 声明插件元数据及 Codex 的默认运行配置喵。 |
| plugin/plugins/codex_adapter/session.py | 提供持久化会话索引、恢复匹配及清理操作喵。 |
| plugin/plugins/codex_adapter/codex_home.py | 管理 CODEX_HOME、共享配置和认证文件初始化喵。 |
Sequence Diagram
sequenceDiagram
participant L as N.E.K.O LLM
participant P as Codex Adapter
participant S as Session Store
participant C as Codex CLI
L->>P: codex_execute(prompt, cwd, options)
P->>S: find resumable thread
P->>C: codex exec / resume
C-->>P: JSONL events and thread_id
P->>S: upsert session state
P-->>L: final text, usage, session_id
Reviews (6): Last reviewed commit: "fix(codex_adapter): 改进时区处理逻辑" | Re-trigger Greptile
| proc.kill() | ||
| await proc.wait() |
There was a problem hiding this comment.
Timeout leaves descendants running
When Codex reaches the timeout after spawning a test runner, compiler, shell script, or development server, this branch kills only the direct Codex process because it was not placed in a separate process group, leaving descendants able to modify files, consume resources, or hold ports after the tool reports a timeout喵。
Knowledge Base Used: Plugin System
There was a problem hiding this comment.
Pull request overview
该 PR 新增 codex_adapter 插件,将 OpenAI Codex CLI 封装为 N.E.K.O 可调用的 @llm_tool 工具集,并实现会话持久化、CODEX_HOME 管理、JSONL 输出解析与错误分类/重试逻辑,以支持在主项目中直接触发 Codex 执行开发任务。
Changes:
- 新增 Codex CLI 子进程执行器与参数构建(支持
--search、fast mode、超时处理等)。 - 新增 JSONL 事件解析器与数据模型(会话、用量统计、执行结果等)。
- 新增会话管理与 CODEX_HOME 管理,并提供插件工具入口与默认配置。
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| plugin/plugins/codex_adapter/init.py | 插件主类与 @llm_tool 暴露、启动初始化、执行与重试、健康检查/会话管理入口 |
| plugin/plugins/codex_adapter/plugin.toml | 插件元信息、SDK 兼容范围与 Codex 适配器默认配置项 |
| plugin/plugins/codex_adapter/executor.py | Codex CLI 检测、参数构建、子进程执行与 stdout/stderr 处理 |
| plugin/plugins/codex_adapter/parser.py | Codex CLI exec --json JSONL 事件逐行解析与汇总 |
| plugin/plugins/codex_adapter/errors.py | Codex CLI 错误模式匹配、分类与重试判定/重试时间提取 |
| plugin/plugins/codex_adapter/session.py | 会话记录持久化、可恢复会话判定与提示包签名计算 |
| plugin/plugins/codex_adapter/models.py | 配置/会话/用量/执行结果等 dataclass 定义与序列化 |
| plugin/plugins/codex_adapter/codex_home.py | CODEX_HOME 解析、seed 初始化、auth.json 写入与环境变量构建 |
Comments suppressed due to low confidence (1)
plugin/plugins/codex_adapter/codex_home.py:327
prepare_managed_codex_home_async()usesasyncio.get_event_loop()inside async code; preferasyncio.get_running_loop()to avoid deprecation warnings and wrong-loop edge cases.
return await asyncio.get_event_loop().run_in_executor(
None,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # 绕过审批和沙箱(--dangerously-bypass-approvals-and-sandbox)。 | ||
| # 仅用于受信任的本地开发场景。 | ||
| dangerously_bypass_approvals_and_sandbox = true |
| effective_codex_home = resolve_effective_codex_home(config.codex_home) | ||
| env_overrides = build_codex_home_env(effective_codex_home) |
| import asyncio | ||
|
|
||
| return await asyncio.get_event_loop().run_in_executor(None, os.path.exists, candidate) |
| created_at: float | ||
| """会话首次创建的 monotonic 时间戳。""" | ||
|
|
||
| last_used_at: float | ||
| """会话最近一次成功使用的时间戳。""" |
| 签名基于影响 Codex CLI 上下文构建的"环境"因素: | ||
| - instructions 文件路径 | ||
| - CODEX_HOME 目录 | ||
| - 默认模型 | ||
| - 额外标记 |
| """ | ||
| assert self._executor is not None and self._session_mgr is not None | ||
|
|
||
| effective_cwd = cwd or self._config.cwd or os.getcwd() |
| signature = compute_prompt_signature( | ||
| instructions_file_path=self._config.instructions_file_path, | ||
| codex_home=self._config.codex_home, | ||
| model=self._config.model, | ||
| ) |
| except _asyncio.TimeoutError: | ||
| try: | ||
| proc.kill() | ||
| except Exception: | ||
| pass |
| "sessions_loaded": len(self._session_mgr._sessions) | ||
| if self._session_mgr | ||
| else 0, |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1cbcaa90b
ℹ️ 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".
| try: | ||
| return_code = await asyncio.wait_for( | ||
| proc.wait(), timeout=invocation.timeout | ||
| ) | ||
| except asyncio.TimeoutError: |
There was a problem hiding this comment.
Kill Codex when the tool task is cancelled
When an LLM execution reaches the default 300-second limit, plugin/core/host.py::_run_with_watchdog cancels this coroutine at the same timeout configured by the decorator, slightly before the inner wait_for can raise TimeoutError. Because this block handles only TimeoutError and has no cancellation cleanup, the Codex subprocess and its stdout/stderr reader tasks remain alive and can continue modifying files after the caller receives a timeout. Handle CancelledError in a cleanup/finally path that terminates and awaits the process and reader tasks.
Useful? React with 👍 / 👎.
| invocation = CLIInvocation( | ||
| cmd=cmd, | ||
| cwd=effective_cwd, | ||
| stdin_data=prompt.encode("utf-8"), | ||
| timeout=float(config.timeout_sec), |
There was a problem hiding this comment.
Apply the configured instructions file
When [codex].instructions_file_path is configured, the adapter never reads that file or prepends its contents: every invocation still sends only prompt as stdin. Consequently users relying on this option for project constraints or task context silently run Codex without those instructions, despite the configuration documentation promising otherwise.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
plugin/plugins/codex_adapter/executor.py (1)
147-160: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
--search分支重建整个 cmd,重复了 skip_git_repo_check 逻辑喵。建议先算好前缀再统一拼接,少一份将来容易忘记同步的重复代码:
♻️ 建议写法
- cmd: list[str] = [exe_path, "exec", "--json"] - - if skip_git_repo_check: - cmd.append("--skip-git-repo-check") - - # --search 在 paperclip 中是 unshift 到最前面(在 exec 之前), - # 但实际测试中放在 exec 之后也能工作,且更清晰。 - # 为保持与 paperclip 一致,放在 exec --json 之前。 - if effective_search: - # 移除已添加的 "exec", "--json",重新构建 - cmd = [exe_path, "--search", "exec", "--json"] - if skip_git_repo_check: - cmd.append("--skip-git-repo-check") + # --search 与 paperclip 一致,放在 exec 之前 + cmd: list[str] = [exe_path] + if effective_search: + cmd.append("--search") + cmd += ["exec", "--json"] + if skip_git_repo_check: + cmd.append("--skip-git-repo-check")🤖 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/codex_adapter/executor.py` around lines 147 - 160, Update the command construction around effective_search to avoid rebuilding cmd and duplicating the skip_git_repo_check handling. Build the optional --search prefix separately, then assemble it with the existing exec --json arguments and apply the skip-git-repo-check flag through one shared path.plugin/plugins/codex_adapter/__init__.py (1)
137-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value别伸手掏
SessionManager._sessions私有属性喵。第 497-499 行也是同样写法。建议在
SessionManager上加个count()(或__len__)方法对外暴露数量,免得以后改内部存储结构时这里悄悄坏掉。🤖 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/codex_adapter/__init__.py` around lines 137 - 139, 避免在 Codex 适配器中直接访问 SessionManager 的私有属性 _sessions;为 SessionManager 增加公开的 count() 或 __len__() 接口,并更新两个 sessions_loaded 计算位置通过该接口获取会话数量,同时保留 SessionManager 为空时返回 0 的行为。plugin/plugins/codex_adapter/parser.py (1)
234-236: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value把正常事件当解析错误记下来,诊断信息会被淹没喵。
Codex 还会输出
turn.started/item.started等事件,这些都会被记进parse_errors;长会话下这个列表会持续膨胀。建议单独用一个unknown_types计数集合,并给parse_errors设个上限。🤖 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/codex_adapter/parser.py` around lines 234 - 236, Update the unknown-event handling in the parser method containing the _parse_errors append: do not record recognized Codex events such as turn.started and item.started as parse errors, track genuinely unknown event types in a separate unknown_types counter/set, and cap parse_errors at a fixed maximum to prevent unbounded growth during long sessions.
🤖 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/codex_adapter/__init__.py`:
- Around line 320-344: Update the retry branch in the execution flow around
is_retryable: when exec_err.retry_not_before is present and has not expired,
return ExecuteResult with that timestamp instead of immediately continuing. For
retryable transient_upstream errors, add a short bounded exponential
asyncio.sleep before the next attempt; preserve immediate retry for other
retryable errors without a retry timestamp.
- Around line 476-487: 在版本检查的 _asyncio.TimeoutError 处理分支中更新 proc.kill()
后的清理流程,等待 proc 完成退出并确保 communicate() 创建的管道被回收;保留现有超时后的异常容错行为,避免清理失败覆盖版本检查结果。
In `@plugin/plugins/codex_adapter/codex_home.py`:
- Around line 125-133: Update the credential-file write flow around
target.write_text and chmod so the file is created with restrictive 0600
permissions from the outset, eliminating the world-readable creation window.
Preserve the existing Windows behavior and JSON payload, and avoid relying on a
post-write chmod as the primary protection.
In `@plugin/plugins/codex_adapter/errors.py`:
- Around line 266-273: 更新错误分类逻辑中的 return_code 判断:移除将 return_code == -1 识别为
TIMEOUT 的条件,改为仅匹配超时文本,或仅将明确的 SIGKILL/SIGTERM 返回码(-9、-15)视为超时;保留现有
ClassifiedError 构造及 executor.py 已返回的 TIMEOUT 行为。
- Around line 294-313: Reorder the classification logic so the retry_not_before
check and USAGE_LIMIT ClassifiedError return occur before the
is_transient_upstream_error branch. Keep the existing retry metadata and raw
error fields, then let remaining transient errors continue through the
TRANSIENT_UPSTREAM path so usage-limit errors are classified as "usage_limit".
In `@plugin/plugins/codex_adapter/executor.py`:
- Around line 186-199: 在 executor.py 的 CLIInvocation 构建流程中接入
config.instructions_file_path:读取文件内容并前置到 prompt 后再编码为
stdin_data;读取失败时记录日志并降级使用原始 prompt,避免静默忽略该配置。同步确保 __init__.py 的调用链继续传递该配置。
- Around line 291-320: Update the subprocess stream setup and the
_read_stdout/_read_stderr helpers to tolerate oversized JSONL lines: increase
the create_subprocess_exec StreamReader limit sufficiently for large Codex
events, and wrap each reader loop with outer exception handling that logs
failures without allowing the task to terminate silently. Preserve the existing
per-line parsing behavior and stderr collection.
In `@plugin/plugins/codex_adapter/plugin.toml`:
- Around line 64-66: 收紧 Codex 审批与沙箱绕过的所有默认值:在
plugin/plugins/codex_adapter/plugin.toml:64-66 将
dangerously_bypass_approvals_and_sandbox 设为 false,并补充开启后的风险说明;在
plugin/plugins/codex_adapter/models.py:84-85 将
AdapterConfig.dangerously_bypass_approvals_and_sandbox 默认值及 from_config_dict 中
_bool 的回退值都改为 False,确保配置缺失时仍保持安全。
---
Nitpick comments:
In `@plugin/plugins/codex_adapter/__init__.py`:
- Around line 137-139: 避免在 Codex 适配器中直接访问 SessionManager 的私有属性 _sessions;为
SessionManager 增加公开的 count() 或 __len__() 接口,并更新两个 sessions_loaded
计算位置通过该接口获取会话数量,同时保留 SessionManager 为空时返回 0 的行为。
In `@plugin/plugins/codex_adapter/executor.py`:
- Around line 147-160: Update the command construction around effective_search
to avoid rebuilding cmd and duplicating the skip_git_repo_check handling. Build
the optional --search prefix separately, then assemble it with the existing exec
--json arguments and apply the skip-git-repo-check flag through one shared path.
In `@plugin/plugins/codex_adapter/parser.py`:
- Around line 234-236: Update the unknown-event handling in the parser method
containing the _parse_errors append: do not record recognized Codex events such
as turn.started and item.started as parse errors, track genuinely unknown event
types in a separate unknown_types counter/set, and cap parse_errors at a fixed
maximum to prevent unbounded growth during long sessions.
🪄 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: ceb45014-6a39-4be1-9e75-7037c6d58d2f
⛔ Files ignored due to path filters (1)
plugin/plugins/codex_adapter/data/store.dbis excluded by!**/*.db
📒 Files selected for processing (8)
plugin/plugins/codex_adapter/__init__.pyplugin/plugins/codex_adapter/codex_home.pyplugin/plugins/codex_adapter/errors.pyplugin/plugins/codex_adapter/executor.pyplugin/plugins/codex_adapter/models.pyplugin/plugins/codex_adapter/parser.pyplugin/plugins/codex_adapter/plugin.tomlplugin/plugins/codex_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_thread_id: | ||
| try: | ||
| await self._session_mgr.mark_error( | ||
| resume_thread_id, exec_err.kind | ||
| ) | ||
| except Exception: | ||
| pass | ||
|
|
||
| # 不可重试 — 立即返回 | ||
| if not is_retryable(exec_err.kind): | ||
| return ExecuteResult( | ||
| session_id=resume_thread_id, | ||
| error_kind=exec_err.kind, | ||
| error_message=exec_err.message, | ||
| retry_not_before=exec_err.retry_not_before, | ||
| final_text=stream.final_text, | ||
| duration_ms=duration_ms, | ||
| ) | ||
|
|
||
| # 可重试 — 继续下一轮 | ||
| continue |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
限流/用量超限时立刻重打,等于往上游火上浇油喵。
transient_upstream 和 usage_limit 都在 RETRYABLE_ERRORS 里,但这里判完 is_retryable 就直接 continue,既没有退避,也完全无视 exec_err.retry_not_before。被 429 时会连着再打 max_retries 次,同时每次都要重新跑一遍 CLI(可能各消耗到 300s 超时)。
建议:retry_not_before 已过期才重试、否则直接返回并把时间告诉调用方;同时对瞬态错误加一个小的指数退避 await asyncio.sleep(...)。
🤖 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/codex_adapter/__init__.py` around lines 320 - 344, Update the
retry branch in the execution flow around is_retryable: when
exec_err.retry_not_before is present and has not expired, return ExecuteResult
with that timestamp instead of immediately continuing. For retryable
transient_upstream errors, add a short bounded exponential asyncio.sleep before
the next attempt; preserve immediate retry for other retryable errors without a
retry timestamp.
| async def _read_stderr() -> None: | ||
| if proc.stderr is None: | ||
| return | ||
| while True: | ||
| line = await proc.stderr.readline() | ||
| if not line: | ||
| break | ||
| stderr_lines.append( | ||
| line.decode("utf-8", errors="replace").rstrip("\r\n") | ||
| ) | ||
|
|
||
| # 读取 stdout 并喂给解析器 | ||
| async def _read_stdout() -> None: | ||
| if proc.stdout is None: | ||
| return | ||
| while True: | ||
| line = await proc.stdout.readline() | ||
| if not line: | ||
| break | ||
| try: | ||
| parser.parse_line(line) | ||
| except Exception as e: | ||
| if self.logger is not None: | ||
| try: | ||
| self.logger.warning("Failed to parse stdout line: %s", e) | ||
| except Exception: | ||
| pass | ||
|
|
||
| stderr_task = asyncio.create_task(_read_stderr()) | ||
| stdout_task = asyncio.create_task(_read_stdout()) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
asyncio create_subprocess_exec limit StreamReader readline ValueError separator not found
💡 Result:
The error "ValueError: Separator is not found, and chunk exceed the limit" (or the related asyncio.LimitOverrunError) occurs when using asyncio.StreamReader.readline() or readuntil() on a stream that contains a line longer than the configured buffer limit [1][2][3]. The default limit for asyncio streams is 64 KiB [1][4]. This safety mechanism is designed to prevent excessive memory consumption when reading potentially malicious or unexpected data [5]. To resolve or handle this issue, you have several options: 1. Increase the buffer limit: If you know your subprocess will output lines larger than 64 KiB, you can increase the limit argument when creating the subprocess [1][6]. proc = await asyncio.create_subprocess_exec( cmd, stdout=asyncio.subprocess.PIPE, limit=1024 * 1024 # Increase to 1 MiB or more as needed) 2. Manually handle the overrun: If you cannot predict the line length or must handle arbitrarily long data, you can catch the LimitOverrunError (or ValueError in older versions) and consume the remaining data manually [7][3]. The LimitOverrunError object provides an attribute called consumed, which indicates how much data was processed before the limit was reached [7][3]. Example of a manual reading loop: try: line = await proc.stdout.readline except asyncio.LimitOverrunError as e: # Consumed is the amount of data already read that did not contain the separator chunk = await proc.stdout.read(e.consumed) # Process the chunk, or loop until the full line is reconstructed # Note: Use readuntil in a loop to handle large data incrementally [7][2] 3. Avoid line-based reading: If you do not require line-by-line processing, use read() or readexactly() to process the output in fixed-size chunks, bypassing the limitations imposed by readline()'s search for a separator [5]. The limit parameter is explicitly supported by asyncio.create_subprocess_exec and asyncio.create_subprocess_shell to configure the StreamReader buffer size for stdout and stderr [6][8][9].
Citations:
- 1: https://stackoverflow.com/questions/55457370/how-to-avoid-valueerror-separator-is-not-found-and-chunk-exceed-the-limit
- 2: LimitOverrunError: Separator is not found, and chunk exceed the limit ipython/ipython#14005
- 3: https://runebook.dev/en/docs/python/library/asyncio-exceptions/asyncio.LimitOverrunError.consumed
- 4: https://docs.python.org/3.11/library/asyncio-stream.html
- 5: https://runebook.dev/en/docs/python/library/asyncio-stream/asyncio.StreamReader.readline
- 6: https://docs.python.org/3/library/asyncio-subprocess.html
- 7: https://stackoverflow.com/questions/78089594/python-reading-long-lines-with-asyncio-streamreader-readline
- 8: https://docs.python.org/3.11/library/asyncio-subprocess.html
- 9: https://docs.python.org/3.10/library/asyncio-subprocess.html
超长 JSONL 行会让 stdout 读取任务直接炸掉喵。
create_subprocess_exec 的 StreamReader 默认 limit 是 64 KiB,readline() 遇到更长的 JSONL 行会抛 ValueError / asyncio.LimitOverrunError。Codex 的 item.completed 事件塞大段 diff 或长回复很容易超过这个值,而异常发生在 readline()(不在 try 内部),_read_stdout 会整体失败,后续事件包括 turn.completed 的 usage 和最终文本都可能丢失,最后被当成“成功但无输出”喵。
🔧 建议:提高 limit 并兜住读取异常
proc = await asyncio.create_subprocess_exec(
*invocation.cmd,
cwd=invocation.cwd,
env=env,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
+ limit=8 * 1024 * 1024,
)并在 _read_stdout / _read_stderr 的循环外层包一层 try/except Exception 记日志,避免单行异常终止整个读取喵。
🤖 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/codex_adapter/executor.py` around lines 291 - 320, Update the
subprocess stream setup and the _read_stdout/_read_stderr helpers to tolerate
oversized JSONL lines: increase the create_subprocess_exec StreamReader limit
sufficiently for large Codex events, and wrap each reader loop with outer
exception handling that logs failures without allowing the task to terminate
silently. Preserve the existing per-line parsing behavior and stderr collection.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/codex_adapter/__init__.py`:
- Around line 371-377: Update the transient upstream retry backoff calculation
in the exec_err handling branch to use 2 ** (attempt + 1), preserving the
documented 2, 4, 8-second sequence and existing maximum cap.
In `@plugin/plugins/codex_adapter/codex_home.py`:
- Around line 135-144: Update the file-writing logic around the fdopen call to
avoid manually closing fd after the context manager may already have closed it.
Prefer opening the target through ordinary open with UTF-8 encoding, or
otherwise verify fd still belongs to the just-opened file before closing it;
preserve exception propagation and JSON output 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: 7b1b7a18-1f1d-4fc2-98e1-9d2e9238d5e0
📒 Files selected for processing (8)
plugin/plugins/codex_adapter/__init__.pyplugin/plugins/codex_adapter/codex_home.pyplugin/plugins/codex_adapter/errors.pyplugin/plugins/codex_adapter/executor.pyplugin/plugins/codex_adapter/models.pyplugin/plugins/codex_adapter/parser.pyplugin/plugins/codex_adapter/plugin.tomlplugin/plugins/codex_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/codex_adapter/plugin.toml
- plugin/plugins/codex_adapter/session.py
- plugin/plugins/codex_adapter/models.py
- plugin/plugins/codex_adapter/parser.py
dac2dd8 to
262655e
Compare
本次修改说明已推送更新到 主要修改安全修复(2 项)
Bug 修复(7 项)
代码质量改进(3 项)
关于
|
1. 修正退避序列:attempt=0 时 2^(0+1)=2s,确保 2,4,8s 序列 2. 修复 fd 所有权管理:os.fdopen 成功后 fd 由文件对象接管, 避免 double-close;使用 try/else 结构清晰处理各阶段失败
| return_code = await asyncio.wait_for( | ||
| proc.wait(), timeout=invocation.timeout | ||
| ) | ||
| except asyncio.TimeoutError: | ||
| # 超时 — 杀死进程 | ||
| try: | ||
| proc.kill() |
There was a problem hiding this comment.
Watchdog cancellation orphans Codex
When a Codex invocation reaches the 300-second LLM-tool watchdog, the outer timeout cancels executor.execute() before its equal-duration inner timeout completes. Because this branch terminates the process only for asyncio.TimeoutError and does not clean up on cancellation, Codex continues modifying files, using network and compute resources, or holding ports after the caller receives a timeout喵。
Knowledge Base Used: Plugin System
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/codex_adapter/__init__.py`:
- Around line 360-378: 在重试处理逻辑中,先判断当前 attempt 是否已经是最后一次尝试;若是则跳过 retry_not_before
和瞬态错误的退避等待,并直接结束循环以保留原始错误。仅在仍有后续尝试时执行现有的 _compute_retry_wait_seconds、指数退避和
asyncio.sleep 逻辑。
- Around line 67-79: Update _compute_retry_wait_seconds to normalize timezone
awareness before subtracting target_dt and now_dt: use a timezone-aware current
datetime when retry_not_before includes an offset, while preserving
naive-datetime behavior otherwise. Keep the existing nonnegative integer wait
result and fallback to 0 for invalid timestamps.
In `@plugin/plugins/codex_adapter/executor.py`:
- Around line 149-153: 移除 executor.py 中 effective_search 分支对
cmd.append("--search") 的追加逻辑,确保命令始终直接进入 cmd.extend(["exec", "--json"]),避免生成无效的
codex --search exec 参数。
🪄 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: d6beef5b-d85d-43e8-835e-cbcf95c675ac
📒 Files selected for processing (8)
plugin/plugins/codex_adapter/__init__.pyplugin/plugins/codex_adapter/codex_home.pyplugin/plugins/codex_adapter/errors.pyplugin/plugins/codex_adapter/executor.pyplugin/plugins/codex_adapter/models.pyplugin/plugins/codex_adapter/parser.pyplugin/plugins/codex_adapter/plugin.tomlplugin/plugins/codex_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/codex_adapter/session.py
- plugin/plugins/codex_adapter/models.py
- plugin/plugins/codex_adapter/errors.py
- plugin/plugins/codex_adapter/parser.py
| # --search 在 paperclip 中是 unshift 到最前面(在 exec 之前) | ||
| if effective_search: | ||
| cmd.append("--search") | ||
|
|
||
| cmd.extend(["exec", "--json"]) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/sh
codex --version
codex --search exec --json --help
codex exec --search --json --helpRepository: Project-N-E-K-O/N.E.K.O
Length of output: 3733
🏁 Script executed:
#!/bin/sh
set -e
printf 'Relevant dependency/config references:\n'
rg -n '"codex|codex-cli|codex|CLAUDE_CODE|CODEX|Codex|codex.exe|codex' --glob '!target/**' --glob '!dist/**' --glob '!build/**' .
printf '\nexecutor.py relevant section:\n'
cat -n plugin/plugins/codex_adapter/executor.py | sed -n '1,240p'Repository: Project-N-E-K-O/N.E.K.O
Length of output: 50379
🏁 Script executed (no clone):
#!/bin/sh
set -e
printf 'Check Codex CLI options for search-related flags:\n'
codex exec --help \
| awk '
BEGIN { in_opts=0 }
/^Options:/ { in_opts=1; next }
/^Commands:/ { exit }
in_opts { print }
' \
| grep -iE 'search|web|context|include|prompt|stdin|message|image|model|config' || true
Length of output: 175
🏁 Script executed (no clone):
#!/bin/sh
set -e
printf 'Check whether any codex exec search flag and parser call exist:\n'
codex exec --help \
| grep -iE 'search|web|context|include|message|stdin|prompt' || true
printf '\nCheck codex local args mapping:\n'
printf 'Repository does not contain codex CLI argument contracts; local `codex exec --search` exits with error.\n'
Length of output: 851
移除无效的 --search 参数喵。
当前环境中 codex exec --search 会报 unexpected argument '--search';这里生成 codex --search exec --json 会让启用搜索的执行路径失败喵。
🤖 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/codex_adapter/executor.py` around lines 149 - 153, 移除
executor.py 中 effective_search 分支对 cmd.append("--search") 的追加逻辑,确保命令始终直接进入
cmd.extend(["exec", "--json"]),避免生成无效的 codex --search exec 参数。
1. 最后一次尝试失败时跳过退避等待,避免无意义等待 2. _compute_retry_wait_seconds 正确处理时区感知 datetime, 防止 timezone-aware/naive 混合减法报错 不采纳第 3 条建议(移除 --search append),因为 --search 是 Codex CLI 的合法参数,放在 exec 之前是正确用法。
修复 CodeRabbit 第三轮反馈已推送更新到 修复内容1. 最后一次尝试失败时跳过退避等待问题:当所有重试都失败时,代码仍会执行退避等待逻辑,导致无意义的等待。 修复:在退避逻辑前检查是否为最后一次尝试,如果是则直接 is_last_attempt = (attempt + 1) >= max_attempts
if is_last_attempt:
self.logger.info("Last attempt failed, skipping backoff")
break2.
|
使用 UTC 统一处理 timezone-aware datetime,避免不同 tzinfo 对象 之间的比较问题。确保 target_dt 和 now_dt 都在同一时区(UTC)下 进行减法运算。
修复 CodeRabbit 第三轮反馈已推送更新到 修复内容1. 最后一次尝试失败时跳过退避等待问题:当所有重试都失败时,代码仍会执行退避等待逻辑,导致无意义的等待。 修复:在退避逻辑前检查是否为最后一次尝试,如果是则直接 is_last_attempt = (attempt + 1) >= max_attempts
if is_last_attempt:
self.logger.info("Last attempt failed, skipping backoff")
break原因:最后一次尝试失败后,循环会自然结束。提前跳出可以避免无意义的等待,让错误更快返回给调用方。 2.
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bdec7af49e
ℹ️ 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".
| wait_sec, | ||
| exec_err.retry_not_before, | ||
| ) | ||
| await asyncio.sleep(wait_sec) |
There was a problem hiding this comment.
Import asyncio before retry backoff
When Codex returns a retryable usage-limit error with a future retry_not_before (or the transient-upstream branch below) and max_retries > 0, this path executes await asyncio.sleep(...), but this module never imports asyncio. That raises NameError and the public tool returns a generic execution failure instead of performing the advertised automatic retry.
Useful? React with 👍 / 👎.
| 注意:签名不包含 prompt 本身(prompt 每次都不同)。 | ||
| """ | ||
| parts = [ | ||
| f"instructions:{instructions_file_path or ''}", |
There was a problem hiding this comment.
Include instruction contents in the resume signature
For installations using instructions_file_path, editing that file in place does not change this signature because only the path string is hashed. The next call in the same cwd will therefore resume a Codex thread that was created under the old instruction package, despite this manager's stated purpose of abandoning old sessions when the prompt package changes; hash the file contents/metadata or otherwise invalidate the session when the instruction file changes.
Useful? React with 👍 / 👎.
概述
添加 codex_adapter 插件,将 OpenAI Codex CLI 注册为猫娘可调用的工具集。
主要功能
变更内容
测试
Summary by CodeRabbit