一个 Python 脚本,从 Codex 日志提取失败命令和行号

KevinW Lv.2 2026/10/21 19:22:02 编辑于 2026/10/21 20:50:17 浏览 3

Agent 输出几百行日志,想知道哪些 shell 命令没正常结束,可以先按退出码和状态筛一遍。下面这个脚本只读本地文件,输出命令、行号和 item ID,方便回到原日志定位。

适用范围:codex exec --json 的 JSONL 事件输出,Python 3.9 及以上,不装第三方库。先跑样例不需要 Codex、账号或 API。

外层 item.completed 表示这条事件结束;命令可能成功、失败,也可能被拒绝。还要读内层 item.type、status 和 exit_code。脚本把失败、拒绝、无法确认的命令分开列出。

1. 保存为 failed_commands.py

import argparse
import json


def summarize(lines):
    report = {"commands": 0, "failed": [], "declined": [], "unknown": []}
    for lineno, raw in enumerate(lines, 1):
        if not raw.strip():
            continue
        try:
            event = json.loads(raw)
        except json.JSONDecodeError as exc:
            raise ValueError(f"line {lineno}: invalid JSON") from exc
        if not isinstance(event, dict):
            raise ValueError(f"line {lineno}: expected an object")
        if event.get("type") != "item.completed":
            continue
        item = event.get("item")
        if not isinstance(item, dict):
            raise ValueError(f"line {lineno}: missing item object")
        if item.get("type") != "command_execution":
            continue
        report["commands"] += 1
        code, status = item.get("exit_code"), item.get("status")
        if code is not None and type(code) is not int:
            raise ValueError(f"line {lineno}: invalid exit_code")
        if status == "declined":
            bucket = "declined"
        elif status == "failed" or (code is not None and code != 0):
            bucket = "failed"
        elif status == "completed" and code == 0:
            continue
        else:
            bucket = "unknown"
        report[bucket].append({"line": lineno, "id": item.get("id"),
                               "command": item.get("command"),
                               "exit_code": code, "status": status})
    return report


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("log")
    args = parser.parse_args()
    try:
        with open(args.log, encoding="utf-8") as stream:
            report = summarize(stream)
    except (OSError, ValueError) as exc:
        parser.exit(2, f"{exc}\n")
    print(json.dumps(report, ensure_ascii=False, indent=2))

2. 用这三行最小样例验证

保存为 demo.jsonl。这是手工构造的事件样例,用于验证解析逻辑,不是一次真实 Codex 任务的日志。

{"type":"item.started","item":{"id":"a","type":"command_execution","command":"python -c 'raise SystemExit(2)'","aggregated_output":"","exit_code":null,"status":"in_progress"}}
{"type":"item.completed","item":{"id":"a","type":"command_execution","command":"python -c 'raise SystemExit(2)'","aggregated_output":"","exit_code":2,"status":"failed"}}
{"type":"item.completed","item":{"id":"b","type":"command_execution","command":"python -c 'print(1)'","aggregated_output":"1\n","exit_code":0,"status":"completed"}}

执行:

python3 failed_commands.py demo.jsonl

输出应该是:

{
  "commands": 2,
  "failed": [
    {
      "line": 2,
      "id": "a",
      "command": "python -c 'raise SystemExit(2)'",
      "exit_code": 2,
      "status": "failed"
    }
  ],
  "declined": [],
  "unknown": []
}

commands 是已结束的命令事件数,两条命令里只有第二行记录的那条需要排查;第一行的 item.started 不会再计一次。

3. 换成你的日志

已有 Codex CLI 并完成认证时,可以在自己的项目中采集一次 JSONL。下面的重定向写法用于 Bash,只把 stdout 写入文件,不要再加 2>&1 把文本错误混进去:

codex exec --json "只概述当前仓库结构,不修改文件" > run.jsonl
python3 failed_commands.py run.jsonl

也可以直接把第二条命令里的文件名换成已有日志。脚本不会执行日志里的 command 内容,也不会调用模型。

怎么处理输出

  • failed:回到给出的行号,查看该事件的 aggregated_output。非零退出是排查线索;例如 rg 的退出码 1 也可能只是没找到匹配,不应一概当作程序故障。
  • declined:命令没有按预期执行,先查对应的拒绝信息,不把它当成通过。
  • unknown:退出码为空或状态无法确认,保留人工检查,不能把 null 当 0。
  • 遇到损坏的 JSON,程序退出码为 2,stderr 会指出行号,例如 line 2: invalid JSON。先检查日志是否截断、是否混入了非 JSON 文本。

三组列表都是空,也只说明“已结束的命令里没筛出这些情况”。它不检查命令一直没结束、重复拼接的日志、turn.failed、顶层 error,也不证明任务目标或测试已完成;这些要另看原日志和实际验收结果。只适用于上面这种事件结构,不要拿内部 session 文件或其他 Agent 日志直接套用。

样例脚本已在本地运行验证:正常退出、非零退出、failed 且退出码为空、declined、退出码无法确认、开始/更新事件、非命令事件及坏输入均已检查;没有调用真实 Codex 模型。分享原日志前记得检查命令和输出中是否有凭证或私有内容。

参考:OpenAI Codex 非交互模式文档与官方 exec 事件定义,2026 年 10 月 21 日核对。

脚本和样例下载

不想逐段复制的话,可以直接下载:

完整示例包 codex-log-helper.zip

包里有脚本、三行样例、验证程序和中文说明。解压后进入目录,直接运行:

python3 failed_commands.py demo.jsonl

单独下载 failed_commands.py

样例运行结果

图中是附件内固定样例的实际输出排版展示,没有调用模型。原日志第 2 行的命令退出码为 2,因此进入 failed 列表。

分类逻辑示意

评论 0

发表评论

登录后参与讨论

登录

暂无评论