本地终端能跑出结果,搬到 CI 里就报错,或者退出码是 0 但日志里一行扫描结果都没有,这类问题通常不是 Codex Security CLI 本身装不上,而是 CI 的运行时版本、工作目录读权限、扫描目标路径这三处和本地不一致。判断顺序建议倒过来:先证明命令确实被找到并执行了,再证明运行时版本对得上,最后才去怀疑参数写错。命令行参数出错的概率不高,更常见的是把权限或路径问题误判成参数问题,然后反复改命令却没有效果。下文所有命令里的 `codex-security` 是可执行文件名的占位写法,实际名称以安装后 `--help` 输出为准。
本地可跑、CI 报错或零输出时,按四步排查:打印命令路径与版本 → 对齐运行时版本 → 校验工作目录读权限 → 显式指定扫描目标路径。这四步都能从日志里直接验证,一般不需要改流水线结构。先把环境对齐再动命令,可以避免把权限问题当成参数问题来修。边界:子命令名、参数和日志格式随版本变化,需结合安装后的实际输出确认,不要照抄别处的命令。
先在 CI 里打印命令路径与版本
CI 最常见的一种情况是安装步骤没真正执行,或者装到了当前用户 PATH 之外的目录,脚本里只留下一行报错就被后续步骤掩盖。在扫描步骤之前加一段自检,把「命令在哪里、版本是多少、关键环境变量是什么」直接打进日志。
set -x
command -v codex-security || echo 'CLI NOT FOUND IN PATH'
codex-security `--version` || echo 'VERSION CHECK FAILED'
echo "PATH=$PATH"
echo "HOME=$HOME"
echo "PWD=$(pwd)"
echo "USER=$(id -un) UID=$(id -u)"
env | grep -i -E 'codex|security|node|python|pip|npm' | sort
几个判断点:`command -v` 没有任何输出,说明可执行文件不在 PATH 上,常见原因是装进了 `~/.local/bin`、npm 全局 bin 或某个虚拟环境目录,而 CI 运行阶段的 HOME 和安装阶段的 HOME 不是同一个;`--version` 能打印但子命令失败,说明命令被找到但运行时缺依赖,往下看第 2 节。建议把安装命令显式写在同一个 job 的脚本里,而不是指望基础镜像里「应该有」。
对比本地与 CI 的运行时版本
Codex Security CLI 这类工具通常带一个解释器入口,版本不匹配时表现是启动即失败、报语法错误或模块找不到,而不是给出可读的业务报错。对比方法很简单:在本地和 CI 各跑同一组命令,把输出贴在一起看。
codex-security `--version`
node -v 2>/dev/null; python3 -V 2>/dev/null
pip show codex-security 2>/dev/null | head -5
npm ls -g `--depth`=0 2>/dev/null | head -20
head -1 "$(command -v codex-security)" # 看 shebang 指向哪个解释器
出现差异时,优先用版本锁定而不是改命令:Python 项目在 `requirements.txt` 或约束文件里锁版本,Node 项目在 `package.json` 的 engines 字段和锁文件里锁版本,并在 CI 里显式指定解释器路径。特别注意 shebang 这一行——本地 shebang 指向某个 pyenv 或 conda 路径,容器里不存在这个路径,就会出现「明明装了却起不来」。这种情况通常把安装方式改成在当前解释器下安装即可,而不是去补那个不存在的路径。
检查工作目录与读权限
空输出的另一种来源是工作目录不对或目录读不进去。扫描工具在没有可读文件时往往安静地返回,退出码仍是 0,看起来像「扫了但没发现问题」。先确认 CI 里实际站在哪个目录、这个目录里有什么、当前用户能不能读。
id
pwd
ls -la
ls -la .
find . -maxdepth 2 -type f | head -20
test -r . && echo 'cwd READABLE' || echo 'cwd NOT READABLE'
ls -ld /workspace /src 2>/dev/null
容器场景要额外注意挂载目录与宿主目录的差异:宿主上的绝对路径(例如某个用户主目录下的项目路径)在容器里通常映射成另一个路径,配置文件里写死的相对路径、软链接指向的宿主绝对路径都会失效;挂载目录的属主 UID 与容器内运行用户不一致时,目录能 `ls` 但文件读不了,或者干脆看到空目录。判断方法是把 `id -u` 和 `ls -n` 看到的属主数字对一下,不一致就先调运行用户或挂载权限,不要在扫描命令上找原因。
确认扫描目标路径没被忽略规则吃掉
零结果要区分两种:目标路径没命中,和命中了但确实没发现问题。最省事的区分办法是做一次「显式指定路径」与「不指定路径」的对比扫描。
# 不指定目标,走默认配置与忽略规则
codex-security scan 2>&1 | tee scan-default.log
# 显式指定目录,并打开详细输出
codex-security scan ./src `--debug` 2>&1 | tee scan-explicit.log
diff scan-default.log scan-explicit.log | head -40
echo "exit=$?"
如果显式指定 `./src` 后有文件级结果、不指定时什么都没有,问题基本在目标路径解析或忽略规则上:仓库里的 `.gitignore` 类文件、工具自身的忽略配置可能把目录整体排除;符号链接目录默认不跟随的情况也很常见。如果两次都没有任何输出,回到第 3 节确认目录里真的有可读文件。另外建议把退出码单独记录下来,很多 CI 步骤失败是因为脚本里的管道把真实退出码吞掉了,加上 `set -o pipefail` 才能看到。
把跑通的步骤固化成最小 CI 片段
排查完先别急着补全参数,把上面四步能跑通的最小流程固化成一段 job,保证失败能稳定复现、成功有明确证据。四类失败点的对照关系可以先记下来:
| 现象 | 先查什么 | 常见原因 | 处理动作 |
|---|---|---|---|
| 命令未找到或步骤被跳过 | `command -v`、PATH、HOME | 装到非 PATH 目录,安装步骤未在同一 job 执行 | 把安装写进扫描前步骤,或在脚本里显式加路径 |
| 启动即失败、模块或语法报错 | `--version`、解释器版本、shebang | 运行时版本偏低,依赖未安装 | 锁定版本,用当前解释器安装 |
| Permission denied 或目录为空 | `id`、`ls -la`、挂载点 | 容器用户与挂载目录属主不一致,路径写死宿主绝对路径 | 调整运行用户或挂载权限,改用相对路径 |
| 退出码 0 但零结果 | 显式路径与默认路径的输出对比 | 目标路径被忽略规则排除,或未指定目标 | 显式传入扫描目录,打开详细输出 |
下面是一段通用骨架,按实际 CI 平台替换 `steps` 与缓存方式即可,重点在于三个环节:自检、扫描、留存日志。
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 CLI(按实际包管理器替换)
run: |
set -euo pipefail
# 示例:pip install codex-security==<锁定版本>
command -v codex-security
- name: 环境自检
run: |
set -euo pipefail
codex-security `--version`
id; pwd; ls -la
- name: 扫描
run: |
set -o pipefail
codex-security scan ./src `--format` json 2>&1 | tee scan.log
- name: 留存日志
if: always()
uses: actions/upload-artifact@v4
with:
name: scan-log
path: scan.log
验证方式看日志内容而不是看步骤是否变绿:扫描日志里应当出现文件级的扫描结果,也就是具体文件路径以及该文件对应的问题条目;如果只有一行汇总或只有启动信息,说明目标路径没命中,回到第 4 节重做显式路径对比。保留 `scan.log` 作为构建产物,下次再出问题可以直接比对两次日志的差异,比反复改命令更快定位是环境漂移还是路径变化。