Codex CLI 0.163:响应中途改指令、并行只读工具,以及三个代理安全修复

·11 分钟阅读·Evergreen Tools Team

2026 年 10 月 15 日,OpenAI 把 Codex CLI 0.163.0 推上正式版。这个版本最直观的改动是一个交互动作:模型还在回答的时候,你可以直接追加一句话把它拉回来,它立刻改向,而不是排队等这一轮结束。更值得留意的是配套的那半句——中断一轮现在会连带停掉活跃的 Code Mode cell 及其嵌套工具调用。以前你按了停,里面还在跑;现在它会真的停下来。这两件事放在一起,改变的是你按停键的心理成本。

终端窗口中的命令行

可搜索的 /config 会告诉你哪些设置需要重启,这半句比能改设置更省事。

先升级,再读一遍迁移提示

新特性里第一个是可搜索的 /config:跨类别搜偏好项,改完能立即生效的设置当场生效,不能立即生效的会告诉你要重启。最后这半句才是真正省事的部分——以前你只能靠「改了行为没变」来推断它需要重启。

同时它去掉了捆绑的 patched zsh,改用标准的 shell executor。用老 shell 旗标的配置会收到迁移提示。这条我建议你认真看一眼,因为「收到提示」和「配置真的还生效」是两件事,尤其是那种一直躺在 dotfiles 里没人动过的配置。

leader key 现在可以在 /keymap 里配,默认 Ctrl+X,快捷键提示会一直显示到你选择或取消,语音静音挪到了 F9。/daemon 可以从命令行重启受管的背景服务,重启前它会警告可能被打断的工作,然后自动重连。这些都是小改动,但都是每天会碰到的。

# Codex CLI 0.163.0, published 2026-10-15. Upgrade the way you always do, then read
# what actually changed before you trust a long unattended run.
npm install -g @openai/[email protected]
codex --version

# The new /config is searchable across categories, applies the settings it can apply
# immediately, and tells you which changes need a restart. That last part is the
# useful half: previously you found out by watching behaviour not change.
#   > /config
#   > /config sandbox        # filter by category
#
# Retired shell flags now emit a migration notice instead of silently doing nothing.
# 0.163 uses the standard shell executor and no longer bundles a patched zsh, so any
# config that depended on the old shell flags needs a look.

即时改向:该改什么,不该改什么

实际用法是这样:第一轮你让它在一个迁移任务上跑起来,比如「在 staging 数据集上跑一遍迁移并报告 diff 数量」;趁它还在跑,你追加「只处理 orders 表,其他都跳过」。0.163 之前这句话会排队等它做完,现在会立刻生效。

按收益排序,值得改向的只有三类:收窄范围(你一开始要宽了,现在看得见它跑偏)、纠正错误假设(它选错了 schema 或分支)、压制啰嗦(它在写小作文而不是给 patch)。

不值得改向的是样式问题。你会花掉比直接改那个文件更多的注意力去描述你想要的风格,而且还得验证它有没有照做。改向的成本低不等于改向是免费的,注意力才是真正的预算。

# Instant steering: send a follow-up while Codex is responding and it redirects
# right away instead of queueing until the turn ends. Interrupting also stops active
# Code Mode cells and their nested tool calls — that second half is the important one.
#
# Before 0.163, "stop" could leave nested tool calls running. Now the interrupt
# propagates. Which means the cost of being wrong went down, so steer more freely.

# A steering pattern that works well on refactors:
#   turn 1: "Run the migration on the staging dataset and report the diff count."
#   (while it is running) "Scope it to the orders table only, skip anything else."
#
# What to steer on, in order of payoff:
#   1. Scope narrowing       — you over-asked and can see it heading too wide
#   2. Wrong assumption      — it picked the wrong schema or the wrong branch
#   3. Verbosity             — it is writing an essay instead of a patch
#
# What NOT to steer on: styling. You will spend more attention than editing the file.

并行只读工具:省的是冷启动那几秒

独立的只读工具现在可以并行跑:skill 查询、memory 查询和会话历史查询不再互相排队。收益集中在冷启动——以前 agent 要在出第一个 token 之前串行做完四次查找。

但请把这句话读准:只有独立且只读的工作会并行。任何带写入的操作仍然是有序的,这个设计是对的,也正因为如此,你不该把它理解成「agent 改代码变快了」。

数字要自己量。代码里那个函数把串行和并行的总耗时对比出来,四个冷启动查找按 120/90/150/80 毫秒算是 440 毫秒对 150 毫秒。但查找次数和单次延迟完全取决于你到底装了多少 skill 和存了多少 memory,别人报的倍数对你没有参考价值。

# Independent read-only tools now run in parallel: skill lookups, memory lookups and
# conversation-history lookups no longer queue behind each other. The win is latency
# on cold starts, where an agent used to serialise four lookups before its first token.
#
# The constraint that matters: only INDEPENDENT and READ-ONLY work parallelises.
# Anything with a write is still ordered, which is correct and is also why you should
# not read this as "the agent got faster at editing".

def measure_lookup_phase(timings_ms: list[int], parallel: bool) -> dict:
    total = max(timings_ms) if parallel else sum(timings_ms)
    return {
        "lookups": len(timings_ms),
        "serial_baseline_ms": sum(timings_ms),
        "observed_ms": total,
        "ratio": round(sum(timings_ms) / max(1, total), 2),
    }

# Four cold-start lookups at 120/90/150/80 ms
print(measure_lookup_phase([120, 90, 150, 80], parallel=False))   # 440 ms
print(measure_lookup_phase([120, 90, 150, 80], parallel=True))    # 150 ms, 2.93x

# Measure this on your own harness before quoting a number. Lookup counts and latencies
# depend entirely on how many skills and memories you actually have.
深色屏幕上的终端输出

按停就该真的停:中断现在会传播到嵌套工具调用。

三个代理修复,本质是三个安全修复

这一组写在 Bug Fixes 里,但内容是安全的:第一,网络代理现在在 DNS 解析之前就先检查权限。以前主机名可能先被解析,策略才执行——即使请求最终会被拒绝,你的 DNS 解析器也已经被泄露了「我配过这个域名」这件事。

第二,拒绝重复的 Host 头。这属于请求头走私的地盘。第三,凭据别名不能再绕过检查钩子。这三条凑在一起,说的是同一件事:策略检查的执行顺序和覆盖范围被收紧了。

所以最实际的动作是钉住版本。老二进制不会自动获得这些修复,而这类修复不会以「安全公告」的形式出现在你的升级清单里。另外文档这次也讲清了 Linux 沙箱的机制:bubblewrap 做文件系统隔离,seccomp 做系统调用过滤。两套机制、两种失败模式,如果你的 CI 跑在 Linux 上,那一段值得读。

# Three proxy fixes in 0.163 that are security fixes wearing a bug-fix label.
#
# 1. Permission is now checked BEFORE the DNS lookup. Previously a hostname could be
#    resolved before the policy ran, which leaks "this domain exists in my config"
#    to the DNS resolver even when the request would be denied.
# 2. Duplicate Host headers are rejected. Header-smuggling territory.
# 3. Credential aliases can no longer bypass inspection hooks.
#
# Pin this in your config and pin the version: an old binary does not get these.

# codex config (per-project .codex/config.toml)
# [sandbox]
# mode = "workspace-write"
#
# [network]
# # Deny by default; allowlist exactly what the build needs.
# allowed_domains = ["registry.npmjs.org", "pypi.org"]

# The docs also clarified how the Linux sandbox works: bubblewrap for filesystem
# isolation and seccomp for syscall filtering. Two different mechanisms with two
# different failure modes. If your CI is Linux, that paragraph is the one to read.

def verify_policy(deny_all_by_default: bool, allowlist: list[str], attempted: str) -> str:
    if attempted in allowlist and deny_all_by_default:
        return "ALLOW (allowlisted)"
    if deny_all_by_default:
        return "DENY (before DNS)"
    return "ALLOW (permissive config, fix this)"

print(verify_policy(True, ["registry.npmjs.org"], "registry.npmjs.org"))
print(verify_policy(True, ["registry.npmjs.org"], "attacker.example"))
print(verify_policy(False, ["registry.npmjs.org"], "attacker.example"))

恢复会话时的权限,以及两个被修掉的静默超时

恢复已保存的会话时,现在会一并恢复那次会话的权限和网络授权,同时仍然尊重显式覆盖和当前的管理策略。这个顺序很重要:一个被恢复的会话不应该继承比当前机器策略更宽松的权限。如果你在 CI 里跑 codex exec,这段逻辑值得单独验一次。

还有两个修复值得单独点名,因为它们是典型的静默超时制造者。一是 codex exec 不会在一条无关命令执行完之后重复打印同一个文件 diff——以前这会把你对输出流的解析撑大。二是本地命令在子进程一直占着输出管道时也能及时结束,而远程命令会保留迟到的输出,让最后一次 poll 仍然收得到。

如果你有一个「偶尔会刚好卡满整个 timeout」的 CI 步骤,现在可以回去查一下是不是这两条。这类问题的特征是:它从不出错,只是一直等。

# Resuming a saved thread now restores its permissions and network approvals, while
# still respecting explicit overrides and current managed policy. That ordering is the
# whole point: a resumed thread must not inherit a looser policy than the machine has
# right now.
import subprocess, json

def resume_and_inspect(thread_id: str) -> dict:
    out = subprocess.run(
        ["codex", "exec", "--json", "--resume", thread_id, "print the effective policy"],
        capture_output=True, text=True, timeout=120,
    )
    policy = None
    for line in out.stdout.splitlines():
        try:
            evt = json.loads(line)
        except json.JSONDecodeError:
            continue
        if evt.get("type") == "policy":
            policy = evt
    return {"rc": out.returncode, "policy": policy, "stderr": out.stderr[-400:]}

print(resume_and_inspect("thread_abc123"))

# Two more changes worth knowing for CI:
# - codex exec no longer reprints the same file diff after an unrelated command,
#   which used to inflate your parse of the output stream.
# - local commands finish promptly even when a child keeps an output pipe open, and
#   remote commands preserve late-arriving output so the final poll still collects it.
# Both of those were silent timeout generators. If your CI had a flaky step that
# sometimes hung for exactly its timeout, check whether it was this.
网线与交换机端口

DNS 解析之前先查权限,堵住的是配置信息的侧信道。

📌 常见问题 FAQ

「响应中途改向」具体改变的是什么?

以前你在模型回答时发的追加消息会排队,等这一轮结束才被处理。0.163 起它立即生效,Codex 会当场改向。配套的改动是中断一轮会同时停掉活跃的 Code Mode cell 及其嵌套工具调用,而以前这些可能继续在后台运行。

并行只读工具会让改代码变快吗?

不会直接变快。只有独立且只读的工具会并行,比如 skill、memory 和会话历史查询;任何带写入的操作仍然是串行的。收益主要体现在冷启动阶段,省掉出第一个 token 之前的几次串行查询。

为什么要特别关注那三个代理修复?

因为它们是安全修复但写在 Bug Fixes 里:DNS 解析之前先检查权限,避免解析阶段泄露配置信息;拒绝重复 Host 头,堵住请求头走私;凭据别名不能再绕过检查钩子。老版本二进制不会自动获得这些修复,所以要钉住版本。

旧配置里的 shell 旗标会怎样?

0.163 改用标准 shell executor,不再捆绑 patched zsh,使用已退役 shell 旗标的配置会收到迁移提示。提示不等于配置仍然生效,建议逐条核对你在 dotfiles 里长期没动过的相关配置。

恢复会话时会继承原来的权限吗?

会恢复那次会话的权限和网络授权,但同时尊重显式覆盖和当前管理策略。也就是说恢复出来的权限不会比机器现在的策略更宽松。CI 里使用 codex exec --resume 的话,建议单独验证一次实际生效的策略。