Codex 配置来源排查:同一条命令,为何两个仓库不同
从两次相同的 CLI 调用出发,找到配置差异真正来自哪一层,再决定最小改动。
- 记录版本、目录与实际参数
- 区分已存在、已启用与最终获选
- 用 /debug-config 观察来源与策略
- 先找到原因,再提最小修改候选
1. 两个仓库都选 inspect,先别急着改配置
把同一条 Codex 命令复制到另一个仓库,界面上的推理强度却变了。终端历史看起来一致,两份根配置也逐行相同。这时继续在用户配置里补一遍相同的值,很容易把真正的原因藏起来。先查清这一次启动实际经过哪些配置来源,修改才有落点。
本文用两个新建的合成仓库做来源追踪练习:tide-ledger 保存运单标签草稿,ridge-notes 保存步行笔记索引。内容都很小,没有凭据、插件、钩子或外部服务。练习只盯住 model_reasoning_effort 一个键,分别检查工作目录、profile、项目层、信任门控与命令行覆盖。管理要求单独记录,不在本机安装策略。
核查日期为 2026 年 10 月 7 日。本稿执行了 codex-cli 0.159.2 的版本与帮助查询,运行了夹具生成器、10 项离线测试及固定 Schema 校验。没有启动真实模型会话,也没有执行 /debug-config。因此后面的 high、medium、low 都是待验证的预期,不能当成实测结果。
2. 把普通层级和管理要求分别画出来
当前官方配置基础列出的普通优先级,从高到低为 CLI、受信任的项目配置、选中的 profile、用户配置、云端托管默认、系统配置和内置默认。项目范围内,离工作目录更近的配置优先。排查时按同一个键往上追,避免只比较整份文件的名称。
云端默认和管理员要求需要在记录中分开。官方托管配置说明把 requirements 定义为约束;与强制要求冲突的配置可能回退到兼容值,并给出通知。不要把“CLI 层最高”理解为可以越过策略。本文不创建 requirements.toml,只给已有管理来源留一个观察栏。
表中有一层,也还需要知道它有没有启用。来源诊断输出显示的路径、禁用原因、实际值以及启动通知,应该保存在同一次运行记录里。仅凭磁盘上有 .codex/config.toml,尚不能确认本轮读取了它。
3. 先核对真正可用的诊断入口
官方命令参考提供交互斜杠命令 /debug-config:查看由低到高的配置层顺序、启停状态及策略来源。它输入在 Codex 会话里。系统终端中的 codex debug 是另一组入口,本机 0.159.2 帮助只列出 models、app-server、prompt-input;本稿没有找到 codex debug config 或 codex config dump 的依据。
codex --version
codex --help
codex debug --help以上三条已在作者环境执行并保存输出。帮助查询出现无法创建 PATH aliases 的只读文件系统警告,版本和 Usage 仍正常返回;没有为消除警告修改权限。它们只核对命令入口,不会给出本文两个仓库的最终配置。
# 下面输入在已获准启动的 Codex 交互会话中
/debug-config这里的斜杠命令尚未执行。不要为了得到一份“完整诊断”顺手收集认证状态、历史会话或整个环境变量表。若当前客户端不能打开该诊断,先保留版本与帮助,再用静态清单缩小范围;不要让模型口头猜测自己加载了哪个文件。
4. 准备一份不会碰真实配置的目录
需要 Python 3.11 或更新版本和 Git。把文末三份完整程序放在同一个目录,然后选择一个尚不存在的绝对路径。生成器在目标下创建合成文件和两个空 Git 仓库。Git 子进程使用单独的最小环境,不继承 GIT_DIR、GIT_CONFIG_* 等变量,并禁用系统配置、用户配置和模板;路径已规范化,带空格也可用。遇到已有目录会停止,不会清空或覆盖。
python prepare.py "/absolute/path/origin-lab"
python inventory.py "/absolute/path/origin-lab"
python -m unittest -vorigin-lab/
├── codex-home/
│ ├── config.toml
│ └── inspect.config.toml
├── tide-ledger/
│ ├── .git/
│ ├── .codex/config.toml
│ ├── README.md
│ └── app/
│ ├── .codex/config.toml
│ └── manifest.txt
├── ridge-notes/
│ ├── .git/
│ ├── .codex/config.toml
│ ├── README.md
│ └── app/manifest.txt
└── evidence/observation.jsoncodex-home 在这里只是普通夹具目录。运行生成器不会让它成为当前用户的 CODEX_HOME,也不会改变任何真实项目的 trust。不要把整个真实 ~/.codex 复制过来;其中可能有认证与会话资料。真正启动前,需要在你已经获准使用、具有合适认证方式的隔离环境里安排测试。登录受阻时就记录阻塞,不要求读者抄凭据。
5. 先逐份读完这五个配置文件
下面以 /absolute/path/origin-lab 为例。生成器会用实际绝对路径替换两个 projects 键。信任差异是刻意写进合成文件的题目条件,不能照搬到现有仓库。这里也没有设置模型名,真实运行前须确认账户、默认模型及推理强度的支持情况。
# codex-home/config.toml
model_reasoning_effort = "low"
web_search = "disabled"
approval_policy = "on-request"
sandbox_mode = "read-only"
[projects."/absolute/path/origin-lab/tide-ledger"]
trust_level = "trusted"
[projects."/absolute/path/origin-lab/ridge-notes"]
trust_level = "untrusted"# codex-home/inspect.config.toml
model_reasoning_effort = "medium"
# tide-ledger/.codex/config.toml(独立文件)
model_reasoning_effort = "high"
# tide-ledger/app/.codex/config.toml(独立文件)
model_reasoning_effort = "low"
# ridge-notes/.codex/config.toml(独立文件)
model_reasoning_effort = "high"后三份文件各只有一行配置,上面合并展示是为了便于比较,不要把三个同名键粘进一个 TOML 文件。当前高级配置说明要求 profile 使用独立的 NAME.config.toml,并在文件顶层写配置键;0.134.0 之后的 --profile 不再读取旧式 [profiles.NAME]。CODEX_HOME 也决定配置和本地状态位置,换了它就需要重新检查认证与状态范围。
配置参考说明 projects.<path>.trust_level 的受信任和不受信任状态。这里检查的是项目 .codex 层是否进入加载过程,不讨论 AGENTS.md 的指令发现,也不通过写入尝试测试沙箱。
6. 按同一份任务单比较两次启动
先审阅夹具,确认要在哪个隔离环境进行真实测试。以下 Bash/Zsh 模板全部未执行;它们会启动 Codex,可能创建本地状态并使用服务,不是纯 TOML 检查。启动时如果出现认证、信任或新增权限询问,停下来记录,不要为了对齐预期自动接受。
LAB="/absolute/path/origin-lab"
( cd "$LAB/tide-ledger/app" && CODEX_HOME="$LAB/codex-home" codex --profile inspect )
( cd "$LAB/ridge-notes/app" && CODEX_HOME="$LAB/codex-home" codex --profile inspect )两条命令中的 CLI 调用相同,只有进入的目录不同。每次打开后先输入 /debug-config,记下该轮 cwd、项目根、选中 profile、层路径、启停原因和策略通知。不要先发送修复任务。若诊断没有展示某个键的最终值,就把该栏留作未知,注明还缺什么观察,不能用预期填空。
任务一:从 tide-ledger/app 启动,寻找根层和 app 层的两个同名键。在普通层都启用且没有相关额外约束的前提下,待验证的候选是 low。接着从 tide-ledger 根目录启动同样的命令,检查 app 层是否离开本次路径;预期候选改为 high。只改变 cwd,其他条件保持一致。
任务二:从 ridge-notes/app 启动,重点寻找项目层被跳过的原因。合成用户配置标记它为 untrusted;如果该状态被本机版本按预期识别,根层的 high 不应参与普通项目覆盖,profile 的 medium 成为候选。若实际出现 high,先核对路径身份、CODEX_HOME 与真实诊断,不要马上把信任改成 trusted。
任务三:回到 tide-ledger/app,检查 profile 确实被找到。即使 profile 的 medium 已加载,项目层仍可能覆盖它。看到 low 时,需要区分“profile 没读到”和“profile 已读到但同键被后层覆盖”,这两种情况要改的文件不同。
7. 一次覆盖可以帮助定位,但不能代替来源解释
完成前面的来源记录后,可以在另一个新会话中只增加一个普通设置覆盖。下面仍是未执行模板;目标是让诊断里出现 CLI 层,不测试权限扩大。
( cd "$LAB/tide-ledger/app" && CODEX_HOME="$LAB/codex-home" codex --profile inspect -c 'model_reasoning_effort="medium"' )预期要观察的是 CLI 来源与 medium 候选,而不是宣称所有环境必定采用这个值。若安装版本不支持该键、模型不支持该强度,或者管理要求介入,应先记录实际错误和通知。继续补更多 -c 参数会让下一次比较失去单变量条件。
同样的命令也可能由不同启动器拼出来。排查真实项目时,记录完整参数数组和实际可执行路径;shell 别名、包装脚本、IDE 启动设置都可能加入参数。只保留与本案有关且已脱敏的项,不运行 env 全量导出,不把内部地址或凭据写入公开工单。
8. 按键填写诊断记录,再决定改哪里
配套 observation.json 的观察结果字段为空,observed_value 是 null;key 和 recheck 保留模板默认值。每个案例复制一份记录,先写声明来源,再填实际观察。下面是完整模板;它没有任何真实运行结果。
{
"case": "",
"cli_version": "",
"executable": "",
"cwd": "",
"project_root": "",
"codex_home": "",
"argv": [],
"selected_profile": "",
"layers_low_to_high": [],
"disabled_layers_and_reasons": [],
"requirements_sources": [],
"key": "model_reasoning_effort",
"candidate_sources": [],
"observed_value": null,
"evidence_excerpt_redacted": "",
"explanation": "",
"smallest_change_candidate": "",
"recheck": "not_run"
}
最小修改候选要对应已确认的原因。若启动目录误入子目录,修正启动路径比改用户默认更准确;若 app 配置保留了过期强度,审阅并修改那一个键;若旧 profile 表还留在用户配置中,按当前格式迁到独立文件,并在单独审阅后清理旧项。这里只形成候选,不自动改现有文件。
当原因落在未信任项目时,先审阅来源和仓库内容。可以保持不受信任,再用允许的用户/profile 配置表达本次需要;不要把“让项目配置生效”直接等同于“应该授予信任”。当来源落在 requirements,向管理员提供脱敏的冲突键、策略来源和通知,不能通过换目录、换账号或覆盖参数绕过。
改动获准后,保留旧记录,使用同一版本、同一目录和同一条命令重新观察。除了目标值,还检查其他层有没有意外变化。只有本轮真实诊断支持了结果,才把 recheck 改为已验证,并写明证据文件。
9. 离线清单能排除哪些问题
inventory.py 只读取夹具约定的五个文件,输出 SHA-256、声明的强度和顶层键。它不扫描用户主目录,不读取认证文件,不合并配置,不推断项目是否可信,也不生成“最终配置”。路径逃逸和目标文件符号链接会被拒绝;TOML 语法错误由标准库直接报出。
10 项测试覆盖两处 Git 根、继承的 Git 环境和模板不进入初始化、绝对信任路径、相同根声明、仅一侧有嵌套层、空观察结果、拒绝覆盖已有目录、拒绝越界、无效 TOML,以及内容变化引起哈希变化。作者另将五份 TOML 对照固定提交的官方 Schema 验证;Schema、校验依赖与执行程序仅保存在私下 QA,不在正文配套包中。复制正文可以复跑标准库的 10 项测试和 TOML 清单检查,不能据此声称复跑了 Schema 校验。它们能证明配套文件按预定方式生成、语法和字段结构匹配该 Schema;不能证明当前安装二进制最终加载了什么。
本次没有尝试启动交互诊断,真实加载结果统一保留为未执行。文章也没有创建模拟加载器来替代 Codex。若你只能完成离线步骤,交付文件清单、待查层和明确的阻塞原因,已经能让下一位排查者继续工作。
10. 配套文件完整原文
以下三份文件构成全部离线工具,均使用 Python 标准库;Git 仅用于生成两个新仓库。网页中的代码与 ZIP 中的文件保持一致。
prepare.py
"""Create inert synthetic fixtures; never launch Codex or change the user's config."""
import json
import os
import shutil
import pathlib
import subprocess
import sys
def create(target):
git = shutil.which('git')
if git is None:
raise RuntimeError('Git executable not found')
root = pathlib.Path(target).resolve()
root.mkdir(parents=True, exist_ok=False)
a, b = root / 'tide-ledger', root / 'ridge-notes'
q = lambda p: json.dumps(str(p), ensure_ascii=False)
files = {
'codex-home/config.toml': (
'model_reasoning_effort = "low"\n'
'web_search = "disabled"\n'
'approval_policy = "on-request"\n'
'sandbox_mode = "read-only"\n\n'
f'[projects.{q(a)}]\ntrust_level = "trusted"\n\n'
f'[projects.{q(b)}]\ntrust_level = "untrusted"\n'),
'codex-home/inspect.config.toml': 'model_reasoning_effort = "medium"\n',
'tide-ledger/.codex/config.toml': 'model_reasoning_effort = "high"\n',
'tide-ledger/app/.codex/config.toml': 'model_reasoning_effort = "low"\n',
'ridge-notes/.codex/config.toml': 'model_reasoning_effort = "high"\n',
'tide-ledger/README.md': '# Tide Ledger\nSynthetic shipping-label draft project.\n',
'tide-ledger/app/manifest.txt': 'label_format=plain\n',
'ridge-notes/README.md': '# Ridge Notes\nSynthetic walking-note index project.\n',
'ridge-notes/app/manifest.txt': 'note_format=plain\n',
'evidence/observation.json': json.dumps({
'case': '', 'cli_version': '', 'executable': '',
'cwd': '', 'project_root': '', 'codex_home': '',
'argv': [], 'selected_profile': '',
'layers_low_to_high': [], 'disabled_layers_and_reasons': [],
'requirements_sources': [], 'key': 'model_reasoning_effort',
'candidate_sources': [], 'observed_value': None,
'evidence_excerpt_redacted': '', 'explanation': '',
'smallest_change_candidate': '', 'recheck': 'not_run'
}, ensure_ascii=False, indent=2) + '\n',
}
for rel, content in files.items():
p = root / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content, encoding='utf-8')
# Do not inherit GIT_DIR, GIT_CONFIG_*, templates, or user config.
git_env = {
'PATH': os.defpath, 'HOME': str(root), 'XDG_CONFIG_HOME': str(root),
'LC_ALL': 'C', 'GIT_CONFIG_NOSYSTEM': '1',
'GIT_CONFIG_SYSTEM': os.devnull, 'GIT_CONFIG_GLOBAL': os.devnull,
}
for repo in (a, b):
subprocess.run([git, 'init', '--quiet', '--template=', str(repo)],
cwd=root, env=git_env, check=True)
print(root)
return root
if __name__ == '__main__':
if len(sys.argv) != 2:
raise SystemExit('Usage: python prepare.py NEW_DIRECTORY')
create(sys.argv[1])
inventory.py
"""Inventory this article's fixture only. This is NOT a Codex config resolver."""
import hashlib
import json
from pathlib import Path
import sys
import tomllib
FILES = ('codex-home/config.toml', 'codex-home/inspect.config.toml',
'tide-ledger/.codex/config.toml',
'tide-ledger/app/.codex/config.toml',
'ridge-notes/.codex/config.toml')
def inventory(root):
root = Path(root).resolve(strict=True)
rows = []
for rel in FILES:
p = root / rel
if p.is_symlink() or not p.resolve(strict=True).is_relative_to(root):
raise ValueError('fixture path escapes root: ' + rel)
raw = p.read_bytes()
data = tomllib.loads(raw.decode('utf-8'))
rows.append({'file': rel, 'sha256': hashlib.sha256(raw).hexdigest(),
'declared_effort': data.get('model_reasoning_effort'),
'top_level_keys': sorted(data)})
return {'scope': 'fixture files only; no effective config claim', 'files': rows}
if __name__ == '__main__':
if len(sys.argv) != 2:
raise SystemExit('Usage: python inventory.py FIXTURE_DIRECTORY')
print(json.dumps(inventory(sys.argv[1]), ensure_ascii=False, indent=2))
test_fixture.py
import json
import os
from pathlib import Path
import tempfile
import tomllib
import unittest
from unittest.mock import patch
from prepare import create
from inventory import inventory
class FixtureTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.root = create(Path(self.temp.name) / 'fixture with spaces')
def tearDown(self):
self.temp.cleanup()
def test_five_toml_files(self):
self.assertEqual(len(inventory(self.root)['files']), 5)
def test_separate_git_roots_ignore_inherited_environment(self):
outside = Path(self.temp.name) / 'outside-git'
template = Path(self.temp.name) / 'hostile-template'
template.mkdir()
(template / 'unwanted.txt').write_text('must not be copied')
config = Path(self.temp.name) / 'user-gitconfig'
config.write_text('[init]\n templateDir = ' + str(template) + '\n')
dirty = {
'GIT_DIR': str(outside), 'GIT_WORK_TREE': str(outside),
'GIT_COMMON_DIR': str(outside), 'GIT_TEMPLATE_DIR': str(template),
'GIT_CONFIG': str(config), 'GIT_CONFIG_GLOBAL': str(config),
'GIT_CONFIG_SYSTEM': str(config), 'GIT_CONFIG_NOSYSTEM': '0',
'GIT_CONFIG_COUNT': '1', 'GIT_CONFIG_KEY_0': 'init.templateDir',
'GIT_CONFIG_VALUE_0': str(template),
'GIT_CONFIG_PARAMETERS': "'init.templateDir=" + str(template) + "'",
}
with patch.dict(os.environ, dirty):
isolated = create(Path(self.temp.name) / 'isolated')
self.assertFalse(outside.exists())
for root in (self.root, isolated):
for name in ('tide-ledger', 'ridge-notes'):
git_dir = root / name / '.git'
self.assertTrue(git_dir.is_dir())
self.assertFalse((git_dir / 'unwanted.txt').exists())
def test_trust_paths_are_absolute(self):
data = tomllib.loads((self.root/'codex-home/config.toml').read_text())
self.assertEqual(data['projects'][str(self.root/'tide-ledger')]['trust_level'], 'trusted')
self.assertEqual(data['projects'][str(self.root/'ridge-notes')]['trust_level'], 'untrusted')
def test_same_root_declaration(self):
self.assertEqual((self.root/'tide-ledger/.codex/config.toml').read_bytes(),
(self.root/'ridge-notes/.codex/config.toml').read_bytes())
def test_nested_layer_only_in_a(self):
self.assertTrue((self.root/'tide-ledger/app/.codex/config.toml').is_file())
self.assertFalse((self.root/'ridge-notes/app/.codex/config.toml').exists())
def test_no_fake_observation(self):
r = json.loads((self.root/'evidence/observation.json').read_text())
self.assertIsNone(r['observed_value'])
self.assertEqual(r['recheck'], 'not_run')
def test_refuse_existing_target(self):
with self.assertRaises(FileExistsError):
create(self.root)
def test_reject_escape(self):
p = self.root/'ridge-notes/.codex/config.toml'
p.unlink()
p.symlink_to(Path(self.temp.name)/'outside.toml')
with self.assertRaises(ValueError):
inventory(self.root)
def test_invalid_toml_rejected(self):
(self.root/'ridge-notes/.codex/config.toml').write_text('broken = [')
with self.assertRaises(tomllib.TOMLDecodeError):
inventory(self.root)
def test_hash_changes_with_input(self):
before = inventory(self.root)['files'][1]['sha256']
p = self.root/'codex-home/inspect.config.toml'
p.write_text(p.read_text()+'# local note\n')
self.assertNotEqual(before, inventory(self.root)['files'][1]['sha256'])
if __name__ == '__main__':
unittest.main()
11. 补充:往文件末尾加一行,为什么根部还没有它
排查优先级之前,还可以先问一个更小的问题:你刚补上的那一行,解析后真在预想的位置吗?例如,为了保留项目的信任设置,先写下 projects 表,再把新的推理强度追加到文件末尾。搜索能找到 medium,文件也能通过 TOML 解析,前文 inventory.py 的 declared_effort 却仍显示 null。此时还没有证据说是 profile 覆盖了它。
下面用另一组独立的合成文件重现这个差别。/synthetic/tide.ledger 只是 TOML 中的一段字符串,不会被创建、访问或设置信任。01 中的最后一行属于 projects 下的那张项目表,根部没有 model_reasoning_effort。02 只加了空行和注释,归属并没有变化。
inputs/01-misplaced.toml
[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
model_reasoning_effort = "medium"
inputs/02-blank-line.toml
[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
# A blank line and comment do not leave the current table.
model_reasoning_effort = "medium"
TOML 1.0.0 的表规则规定,表头后的键属于该表,直到下一个表头或文件结束;根表位于文档开头。换行和注释都不建立新的表。这里说的是解析出来的结构,不是 Codex 对某个字段的接受规则。
前文清单里的 data.get('model_reasoning_effort') 只读根字典。在这个具体案例中,它返回 None,序列化为 JSON 的 null,表示根部没有这个键;它没有递归搜索项目表。把“搜索到了文本”“根部有字符串值”“应用实际采用了值”分开记录,才能知道下一步应检查格式还是继续追踪加载来源。
12. 同名键不是同一路径,修复也不靠“最后一行”
第三份文件特意在根部保留 low,再在项目表中写 medium。两行虽然末尾的键名相同,完整路径却不同,因此是合法 TOML。解析结果应同时保留两个位置;不能用“最后出现的同名行”替代根部查找,也不能据此声称 medium 覆盖了 low。
inputs/03-root-and-nested.toml
model_reasoning_effort = "low"
[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
model_reasoning_effort = "medium"
如果明确要修复的是 01 的根部设置,04 只把 medium 那一行移到第一个表头之前。项目里的 trust_level 仍为 untrusted,其他结构不动。这个移动解决的是文本归属,不要求授予项目更多信任。如果面对的是 03,根部已有 low,就不能再往前添加一个 medium;应先确认哪个根部值才是本次获准表达的意图,再审阅替换与嵌套项清理。
inputs/04-repaired.toml
model_reasoning_effort = "medium"
[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
05 把两个定义都放在根表,因此产生同一作用域的重复键错误;06 尝试用 [] 返回根表,也会解析失败。这两种结果都不能当作“空配置”继续比较优先级。官方键规则可以用来核对重复定义;错误文字会随解析器版本变化,本练习只固定异常类别。
inputs/05-duplicate-root.toml
model_reasoning_effort = "low"
model_reasoning_effort = "medium"
[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
inputs/06-empty-header.toml
[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
[]
model_reasoning_effort = "medium"
还要防止把排版当成结构。07 使用点分键,它和 04 解析成相同的字典。引号中的 /synthetic/tide.ledger 是一个完整键段,里面的点不会再分层。因此后面的探针用数组保存路径,不把路径拼成一段含义不清的点分字符串。
inputs/07-dotted-control.toml
model_reasoning_effort = "medium"
projects."/synthetic/tide.ledger".trust_level = "untrusted"
08 则把 model_reasoning_effort 变成了一张含 value 的表。根部键确实存在,类型却是 table,不能把它说成“缺失”,也不能从里面挑出 medium 冒充根部字符串。这只是类型边界的反例,不是推荐配置格式;本补充没有对这份文件做 Codex Schema 校验。
inputs/08-root-table-value.toml
model_reasoning_effort.value = "medium"
[projects."/synthetic/tide.ledger"]
trust_level = "untrusted"
13. 用解析树核对八份固定输入
把以上八份文件按文件名保存在 inputs/,把下面 scope_probe.py 放在它旁边。Python 3.11 起提供标准库 tomllib;本次实际运行的是 Python 3.12.14。与运行版本对应的CPython 官方 tomllib 文档原文说明,它解析 TOML 1.0.0,返回字典,无效文档抛出 TOMLDecodeError;它不提供 TOML 写回功能。
探针先解析,再遍历字典和数组。root 单独记录根部是否存在及类型;matches 记录所有精确同名键的位置,数组中的位置还带整数下标。注释或字符串中出现键名不会成为匹配项。遍历顺序固定,方便逐字核对输出。
命令行不接受路径参数,只读取脚本旁八个约定文件。它拒绝入口文件夹或文件本身为符号链接、缺失文件和超过 4096 字节的夹具;这些是方便复现的输入约束,不是一个面向不受信任文件系统的安全扫描器。无效 UTF-8 与 TOML 语法错误分别报告,错误时没有半份解析结果。字符串只回显本题使用的 low、medium、high,其余值省略;这个约束也不意味着可以把真实敏感配置交给它。
scope_probe.py
"""Read this exercise's synthetic files only; never load or change Codex config."""
import hashlib
import json
from pathlib import Path
import sys
import tomllib
KEY = "model_reasoning_effort"
CASES = (
"01-misplaced.toml", "02-blank-line.toml", "03-root-and-nested.toml",
"04-repaired.toml", "05-duplicate-root.toml", "06-empty-header.toml",
"07-dotted-control.toml", "08-root-table-value.toml",
)
ROOT = Path(__file__).resolve().parent
def describe(value):
if isinstance(value, dict):
return {"kind": "table"}
if isinstance(value, list):
return {"kind": "array"}
if isinstance(value, str):
# Do not echo arbitrary values even in this synthetic exercise.
if value in {"low", "medium", "high"}:
return {"kind": "string", "value": value}
return {"kind": "string", "value_omitted": True}
return {"kind": type(value).__name__, "value_omitted": True}
def analyze(raw):
try:
document = tomllib.loads(raw.decode("utf-8"))
except UnicodeDecodeError:
return {"parse": "error", "error": "UnicodeDecodeError"}
except tomllib.TOMLDecodeError:
# Diagnostics may quote input; keep only the stable exception class.
return {"parse": "error", "error": "TOMLDecodeError"}
matches = []
def visit(node, path):
if isinstance(node, dict):
for key in sorted(node):
value = node[key]
here = path + [key]
if key == KEY:
matches.append({"path": here, **describe(value)})
visit(value, here)
elif isinstance(node, list):
for index, value in enumerate(node):
visit(value, path + [index])
visit(document, [])
root = {"present": KEY in document}
if KEY in document:
root.update(describe(document[KEY]))
return {"parse": "ok", "root": root, "matches": matches}
def read_fixture(folder, name):
if name not in CASES:
raise ValueError("unknown fixture name")
folder = Path(folder)
if folder.is_symlink():
raise ValueError("fixture folder must not be a symlink")
base = folder.resolve(strict=True)
path = base / name
if path.is_symlink() or not path.is_file():
raise ValueError("fixture must be a regular non-symlink file")
# Each example is tiny. Bound reads without loading a huge replacement file.
with path.open("rb") as source:
raw = source.read(4097)
if len(raw) > 4096:
raise ValueError("fixture exceeds 4096 bytes")
return raw
def run(folder):
reports, hashes = {}, {}
for name in CASES:
raw = read_fixture(folder, name)
reports[name] = analyze(raw)
hashes[name] = hashlib.sha256(raw).hexdigest()
return reports, hashes
def main():
if len(sys.argv) != 1:
raise SystemExit("No path arguments: this command reads bundled fixtures only")
actual, hashes = run(ROOT / "inputs")
expected = json.loads((ROOT / "expected" / "reports.json").read_text("utf-8"))
print(json.dumps(actual, ensure_ascii=False, indent=2))
print("INPUT_SHA256 " + json.dumps(hashes, sort_keys=True))
if actual != expected:
raise SystemExit("FAIL: review the inputs and fixed oracle; do not regenerate it")
print("PASS: 8 fixed TOML location cases; Codex runtime not tested")
if __name__ == "__main__":
main()
expected/reports.json 是先按八个案例逐项写定的结构预期。它不从 analyze 的返回结果生成。保留紧凑写法是为了便于查看各案例;程序比较的是解析后的 JSON 对象,不要求这份 JSON 的排版与标准输出相同。
expected/reports.json
{
"01-misplaced.toml": {"parse": "ok", "root": {"present": false}, "matches": [{"path": ["projects", "/synthetic/tide.ledger", "model_reasoning_effort"], "kind": "string", "value": "medium"}]},
"02-blank-line.toml": {"parse": "ok", "root": {"present": false}, "matches": [{"path": ["projects", "/synthetic/tide.ledger", "model_reasoning_effort"], "kind": "string", "value": "medium"}]},
"03-root-and-nested.toml": {"parse": "ok", "root": {"present": true, "kind": "string", "value": "low"}, "matches": [{"path": ["model_reasoning_effort"], "kind": "string", "value": "low"}, {"path": ["projects", "/synthetic/tide.ledger", "model_reasoning_effort"], "kind": "string", "value": "medium"}]},
"04-repaired.toml": {"parse": "ok", "root": {"present": true, "kind": "string", "value": "medium"}, "matches": [{"path": ["model_reasoning_effort"], "kind": "string", "value": "medium"}]},
"05-duplicate-root.toml": {"parse": "error", "error": "TOMLDecodeError"},
"06-empty-header.toml": {"parse": "error", "error": "TOMLDecodeError"},
"07-dotted-control.toml": {"parse": "ok", "root": {"present": true, "kind": "string", "value": "medium"}, "matches": [{"path": ["model_reasoning_effort"], "kind": "string", "value": "medium"}]},
"08-root-table-value.toml": {"parse": "ok", "root": {"present": true, "kind": "table"}, "matches": [{"path": ["model_reasoning_effort"], "kind": "table"}]}
}
python3 -B scope_probe.py
下面是这条命令的完整标准输出,同时保存为 expected/output.txt。最后一条 PASS 只表示八份结构报告与固定预期一致;它没有宣告任何 Codex 设置生效。INPUT_SHA256 记录本版八份文件的字节摘要。根部缺失、根部字符串、根部表和解析错误在这份输出里分别可见。
expected/output.txt
{
"01-misplaced.toml": {
"parse": "ok",
"root": {
"present": false
},
"matches": [
{
"path": [
"projects",
"/synthetic/tide.ledger",
"model_reasoning_effort"
],
"kind": "string",
"value": "medium"
}
]
},
"02-blank-line.toml": {
"parse": "ok",
"root": {
"present": false
},
"matches": [
{
"path": [
"projects",
"/synthetic/tide.ledger",
"model_reasoning_effort"
],
"kind": "string",
"value": "medium"
}
]
},
"03-root-and-nested.toml": {
"parse": "ok",
"root": {
"present": true,
"kind": "string",
"value": "low"
},
"matches": [
{
"path": [
"model_reasoning_effort"
],
"kind": "string",
"value": "low"
},
{
"path": [
"projects",
"/synthetic/tide.ledger",
"model_reasoning_effort"
],
"kind": "string",
"value": "medium"
}
]
},
"04-repaired.toml": {
"parse": "ok",
"root": {
"present": true,
"kind": "string",
"value": "medium"
},
"matches": [
{
"path": [
"model_reasoning_effort"
],
"kind": "string",
"value": "medium"
}
]
},
"05-duplicate-root.toml": {
"parse": "error",
"error": "TOMLDecodeError"
},
"06-empty-header.toml": {
"parse": "error",
"error": "TOMLDecodeError"
},
"07-dotted-control.toml": {
"parse": "ok",
"root": {
"present": true,
"kind": "string",
"value": "medium"
},
"matches": [
{
"path": [
"model_reasoning_effort"
],
"kind": "string",
"value": "medium"
}
]
},
"08-root-table-value.toml": {
"parse": "ok",
"root": {
"present": true,
"kind": "table"
},
"matches": [
{
"path": [
"model_reasoning_effort"
],
"kind": "table"
}
]
}
}
INPUT_SHA256 {"01-misplaced.toml": "46c2fa941f0d5cb29e85e5bb8b22e58cab21e8bbb6821d11673546b1f8b596cf", "02-blank-line.toml": "edc5aec7dce7061ffe138f855ca609d88df7427a45e7fe22fb1c88be2a049f68", "03-root-and-nested.toml": "28a3acb415bc3cdf860ca9c845b7d632b18e0c54833c0b268b9849eafbaf56d5", "04-repaired.toml": "7132b626f37a599bba42fc0dc1d7b9216ed82857ab903dac22dcaf0954c95c76", "05-duplicate-root.toml": "a09a5b4c12482ca36588799d68eb66bb995be38197da36d2d12ec7913ebf7bb7", "06-empty-header.toml": "1dcfcc89f50b7b3dfd77d83d2ab9559e5467154a27de690ff3d86817edb6d5dc", "07-dotted-control.toml": "2728dabe8bce25bdbd2302d34e4cecb880819b3d63bbbdd6089e501722058159", "08-root-table-value.toml": "b40ae83f686ff9f3a2c338ae6808627f663be02e90c266345a5f11622415528e"}
PASS: 8 fixed TOML location cases; Codex runtime not tested
14. 给解析结果、文件字节和失败方式各留一份验收
结构相同,不代表文件字节相同。01 与 02 的解析报告相同,摘要不同,正好可以作为控制。scope_probe.py 本身会打印摘要,但只比较结构预期;下面 check_output.py 才把完整 stdout 与固定的 expected/output.txt 逐字节比较,同时检查退出码和 stderr。八个摘要还用独立的 sha256sum 工具核对过。
check_output.py
"""Compare the whole stdout, including byte hashes, with a frozen snapshot."""
from pathlib import Path
import subprocess
import sys
ROOT = Path(__file__).resolve().parent
def main():
if len(sys.argv) != 1:
raise SystemExit("No arguments: check the bundled snapshot only")
command = [sys.executable, "-B"]
if sys.flags.optimize:
command.append("-O")
command.append(str(ROOT / "scope_probe.py"))
result = subprocess.run(command, cwd=ROOT, capture_output=True, timeout=30)
expected = (ROOT / "expected" / "output.txt").read_bytes()
if result.returncode != 0:
raise SystemExit("FAIL: probe exited nonzero; review fixtures and fixed reports")
if result.stderr:
raise SystemExit("FAIL: probe wrote unexpected stderr")
if result.stdout != expected:
raise SystemExit("FAIL: full output differs from the frozen byte snapshot")
print("PASS: full stdout and all 8 input hashes match the frozen byte snapshot")
if __name__ == "__main__":
main()
python3 -B check_output.py
python3 -B -O check_output.py
两条命令都应输出同一句话:
PASS: full stdout and all 8 input hashes match the frozen byte snapshot
若只给夹具加一条注释,结构检查仍可通过,但完整输出检查必须失败。若把 04 的 medium 改成 high,结构检查本身就应失败。这两个负向控制都只在临时副本中执行;验收没有重新生成预期来迁就结果。你若有意扩充练习,应该先写清新的输入与预期,再建立新的快照。
以下 17 项测试覆盖固定报告、空行与摘要、原清单的根部查找、移动前后的无关字段、点分键、根部表类型、表数组下标、注释与字符串、解析错误、未知值隐藏、编码错误、文件边界和只读检查。表数组使用的是 demo 结构,只检验通用 TOML 路径,不是 Codex 配置建议。
test_scope.py
import json
from pathlib import Path
import shutil
import tempfile
import tomllib
import unittest
from scope_probe import ROOT, CASES, KEY, analyze, read_fixture, run
class ScopeTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.folder = Path(self.temp.name) / "inputs"
shutil.copytree(ROOT / "inputs", self.folder)
def tearDown(self):
self.temp.cleanup()
def test_all_eight_fixed_reports(self):
actual, _ = run(self.folder)
expected = json.loads((ROOT / "expected" / "reports.json").read_text("utf-8"))
self.assertEqual(actual, expected)
self.assertEqual(list(actual), list(CASES))
def test_blank_line_changes_hash_not_location(self):
reports, hashes = run(self.folder)
self.assertEqual(reports[CASES[0]], reports[CASES[1]])
self.assertNotEqual(hashes[CASES[0]], hashes[CASES[1]])
def test_original_inventory_root_lookup(self):
bad = tomllib.loads(read_fixture(self.folder, CASES[0]).decode())
fixed = tomllib.loads(read_fixture(self.folder, CASES[3]).decode())
self.assertNotIn(KEY, bad)
self.assertIsNone(bad.get(KEY))
self.assertEqual(fixed.get(KEY), "medium")
def test_recovery_preserves_unrelated_project_fields(self):
bad = tomllib.loads(read_fixture(self.folder, CASES[0]).decode())
fixed = tomllib.loads(read_fixture(self.folder, CASES[3]).decode())
bad["projects"]["/synthetic/tide.ledger"].pop(KEY)
fixed.pop(KEY)
self.assertEqual(bad, fixed)
def test_table_header_and_dotted_key_same_document(self):
a = tomllib.loads(read_fixture(self.folder, CASES[3]).decode())
b = tomllib.loads(read_fixture(self.folder, CASES[6]).decode())
self.assertEqual(a, b)
self.assertEqual(list(a["projects"]), ["/synthetic/tide.ledger"])
def test_root_presence_does_not_mean_scalar(self):
report = analyze(read_fixture(self.folder, CASES[7]))
self.assertEqual(report["root"], {"present": True, "kind": "table"})
def test_array_of_tables_locations_include_index(self):
raw = b'[[demo]]\nmodel_reasoning_effort="low"\n[[demo]]\nmodel_reasoning_effort="high"\n'
report = analyze(raw)
self.assertEqual(report["root"], {"present": False})
self.assertEqual(report["matches"], [
{"path": ["demo", 0, KEY], "kind": "string", "value": "low"},
{"path": ["demo", 1, KEY], "kind": "string", "value": "high"},
])
def test_comment_and_string_do_not_create_a_key(self):
raw = b'# model_reasoning_effort="high"\nexample="model_reasoning_effort"\n'
self.assertEqual(analyze(raw), {"parse": "ok", "root": {"present": False}, "matches": []})
def test_parse_error_has_no_partial_result(self):
report = analyze(read_fixture(self.folder, CASES[4]))
self.assertEqual(report, {"parse": "error", "error": "TOMLDecodeError"})
def test_unknown_scalar_not_echoed(self):
report = analyze(b'model_reasoning_effort="fixture-marker-not-an-effort"\n')
self.assertNotIn("fixture-marker", json.dumps(report))
self.assertTrue(report["root"]["value_omitted"])
def test_bad_utf8_is_not_empty_config(self):
self.assertEqual(analyze(b'\xff'), {"parse": "error", "error": "UnicodeDecodeError"})
def test_unknown_fixture_name_rejected(self):
with self.assertRaisesRegex(ValueError, "unknown fixture"):
read_fixture(self.folder, "../config.toml")
def test_missing_fixture_does_not_become_absence(self):
(self.folder / CASES[0]).unlink()
with self.assertRaisesRegex(ValueError, "regular"):
run(self.folder)
def test_symlink_file_rejected(self):
p = self.folder / CASES[0]
p.unlink()
p.symlink_to(self.folder / CASES[3])
with self.assertRaisesRegex(ValueError, "non-symlink"):
run(self.folder)
def test_symlink_folder_rejected(self):
alias = Path(self.temp.name) / "alias"
alias.symlink_to(self.folder, target_is_directory=True)
with self.assertRaisesRegex(ValueError, "folder"):
run(alias)
def test_oversize_fixture_rejected(self):
(self.folder / CASES[0]).write_bytes(b"#" + b"x" * 4096)
with self.assertRaisesRegex(ValueError, "4096"):
run(self.folder)
def test_probe_does_not_modify_input_files(self):
before = {p.name: p.read_bytes() for p in self.folder.iterdir()}
run(self.folder)
self.assertEqual(before, {p.name: p.read_bytes() for p in self.folder.iterdir()})
if __name__ == "__main__":
unittest.main(verbosity=2)
python3 -B -m unittest -v test_scope
python3 -B -O -m unittest -v test_scope
本次普通模式的完整测试日志如下;耗时只是这次运行的记录,不是固定预期。
test_all_eight_fixed_reports (test_scope.ScopeTests.test_all_eight_fixed_reports) ... ok
test_array_of_tables_locations_include_index (test_scope.ScopeTests.test_array_of_tables_locations_include_index) ... ok
test_bad_utf8_is_not_empty_config (test_scope.ScopeTests.test_bad_utf8_is_not_empty_config) ... ok
test_blank_line_changes_hash_not_location (test_scope.ScopeTests.test_blank_line_changes_hash_not_location) ... ok
test_comment_and_string_do_not_create_a_key (test_scope.ScopeTests.test_comment_and_string_do_not_create_a_key) ... ok
test_missing_fixture_does_not_become_absence (test_scope.ScopeTests.test_missing_fixture_does_not_become_absence) ... ok
test_original_inventory_root_lookup (test_scope.ScopeTests.test_original_inventory_root_lookup) ... ok
test_oversize_fixture_rejected (test_scope.ScopeTests.test_oversize_fixture_rejected) ... ok
test_parse_error_has_no_partial_result (test_scope.ScopeTests.test_parse_error_has_no_partial_result) ... ok
test_probe_does_not_modify_input_files (test_scope.ScopeTests.test_probe_does_not_modify_input_files) ... ok
test_recovery_preserves_unrelated_project_fields (test_scope.ScopeTests.test_recovery_preserves_unrelated_project_fields) ... ok
test_root_presence_does_not_mean_scalar (test_scope.ScopeTests.test_root_presence_does_not_mean_scalar) ... ok
test_symlink_file_rejected (test_scope.ScopeTests.test_symlink_file_rejected) ... ok
test_symlink_folder_rejected (test_scope.ScopeTests.test_symlink_folder_rejected) ... ok
test_table_header_and_dotted_key_same_document (test_scope.ScopeTests.test_table_header_and_dotted_key_same_document) ... ok
test_unknown_fixture_name_rejected (test_scope.ScopeTests.test_unknown_fixture_name_rejected) ... ok
test_unknown_scalar_not_echoed (test_scope.ScopeTests.test_unknown_scalar_not_echoed) ... ok
----------------------------------------------------------------------
Ran 17 tests in 0.011s
OK
测试也要能拒绝错误实现。下面四个变体分别模拟空行切回根部、把嵌套匹配当根部、把根部表误报为缺失、丢掉数组下标。检查器先确认原实现的定向测试通过,再用 unittest.mock 临时替换两个函数查找位置,运行每个错误变体。只有定向测试恰好出现一次 AssertionError、没有错误或跳过,并且原函数已恢复,才算抓住错误;导入失败或其他异常不能算通过。整个检查器成功时退出 0,不修改源码或固定预期。
check_scope_mutants.py
"""Prove four named incorrect implementations fail assertions, not imports.
Runs only bundled synthetic cases. Does not rewrite scope_probe or its oracle.
"""
import json
import re
import unittest
from unittest.mock import patch
import scope_probe
import test_scope
ORIGINAL = scope_probe.analyze
KEY = scope_probe.KEY
def blank_line_resets_table(raw):
report = ORIGINAL(raw)
if report["parse"] != "ok":
return report
# Wrong algorithm: a blank line is incorrectly treated as leaving a table.
inside_table = False
root = {"present": False}
for line in raw.decode("utf-8").splitlines():
line = line.strip()
if not line:
inside_table = False
elif line.startswith("["):
inside_table = True
elif not inside_table:
match = re.fullmatch(KEY + r'\s*=\s*"(low|medium|high)"', line)
if match:
root = {"present": True, "kind": "string", "value": match[1]}
report["root"] = root
return report
def terminal_name_becomes_root(raw):
report = ORIGINAL(raw)
if report["parse"] == "ok":
# Wrong algorithm: the last matching leaf name replaces the root value.
for match in report["matches"]:
report["root"] = {"present": True,
**{k: v for k, v in match.items() if k != "path"}}
return report
def table_value_becomes_absent(raw):
report = ORIGINAL(raw)
if report["parse"] == "ok" and report["root"].get("kind") == "table":
# Wrong algorithm: present-but-wrong-type is collapsed into missing.
report["root"] = {"present": False}
return report
def array_indices_disappear(raw):
report = ORIGINAL(raw)
if report["parse"] == "ok":
# Wrong algorithm: path serialization keeps strings and drops indices.
for match in report["matches"]:
match["path"] = [part for part in match["path"] if not isinstance(part, int)]
return report
MUTANTS = (
("blank_line_resets_table", blank_line_resets_table,
"test_blank_line_changes_hash_not_location"),
("terminal_name_becomes_root", terminal_name_becomes_root,
"test_all_eight_fixed_reports"),
("table_value_becomes_absent", table_value_becomes_absent,
"test_root_presence_does_not_mean_scalar"),
("array_indices_disappear", array_indices_disappear,
"test_array_of_tables_locations_include_index"),
)
def run_tests(names):
suite = unittest.TestSuite(test_scope.ScopeTests(name) for name in names)
result = unittest.TestResult()
suite.run(result)
return result
def main():
baseline = run_tests([name for _, _, name in MUTANTS])
if not baseline.wasSuccessful() or baseline.testsRun != len(MUTANTS):
raise SystemExit("FAIL: baseline directed tests did not all pass")
rows = []
for name, mutation, test_name in MUTANTS:
# run() looks up scope_probe.analyze; direct tests use their imported name.
# Patch both lookup locations, restoring them before the next mutant.
with patch.object(scope_probe, "analyze", mutation), \
patch.object(test_scope, "analyze", mutation):
result = run_tests([test_name])
if (result.testsRun != 1 or result.errors or result.skipped
or len(result.failures) != 1
or "AssertionError" not in result.failures[0][1]):
raise SystemExit("FAIL: mutant not caught by an assertion: " + name)
rows.append({"mutant": name, "test": test_name,
"outcome": "assertion_failure", "errors": 0})
if scope_probe.analyze is not ORIGINAL or test_scope.analyze is not ORIGINAL:
raise SystemExit("FAIL: patched functions were not restored")
print(json.dumps({"baseline_directed_tests_passed": len(MUTANTS),
"mutants": rows}, indent=2))
print("PASS: 4 mutants caught by assertion failures; 0 test errors")
if __name__ == "__main__":
main()
python3 -B check_scope_mutants.py
python3 -B -O check_scope_mutants.py
普通模式下的完整变体检查输出如下;优化模式也通过同样检查:
{
"baseline_directed_tests_passed": 4,
"mutants": [
{
"mutant": "blank_line_resets_table",
"test": "test_blank_line_changes_hash_not_location",
"outcome": "assertion_failure",
"errors": 0
},
{
"mutant": "terminal_name_becomes_root",
"test": "test_all_eight_fixed_reports",
"outcome": "assertion_failure",
"errors": 0
},
{
"mutant": "table_value_becomes_absent",
"test": "test_root_presence_does_not_mean_scalar",
"outcome": "assertion_failure",
"errors": 0
},
{
"mutant": "array_indices_disappear",
"test": "test_array_of_tables_locations_include_index",
"outcome": "assertion_failure",
"errors": 0
}
]
}
PASS: 4 mutants caught by assertion failures; 0 test errors
本次将源码目录、从新增正文代码块重新提取的目录、公开 ZIP 解压目录分别复跑。三处的 17 项测试、八份报告、完整 stdout 比较和四个错误变体均通过;又各自用 -O 运行一次,确认关键判定不依赖可被优化掉的裸 assert。测试数不包含前文的 10 项原练习,也没有追加 Schema 或运行时验收。
15. 回到原清单检查,保留真实加载的未知项
最后把结果接回原清单,避免只证明一个新工具。下面 inventory_scope_demo.py 导入前文未经修改的 inventory.py,在新的临时目录中建立它约定的五个普通文件。除用户配置位置依次放入 01、04,其余四份都固定为一行 model_reasoning_effort = "high"。它不调用 prepare.py,不初始化 Git,不启动 Codex,临时文件离开上下文后清理。
inventory_scope_demo.py
"""Run the article's unchanged inventory over temporary, inert local files."""
import json
from pathlib import Path
import tempfile
from inventory import FILES, inventory
from scope_probe import ROOT, read_fixture
def demo():
result = {}
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
# Exactly the five known files, no Git init and no Codex process.
for rel in FILES:
target = root / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(b'model_reasoning_effort="high"\n')
for label, name in (("misplaced", "01-misplaced.toml"),
("repaired", "04-repaired.toml")):
(root / FILES[0]).write_bytes(read_fixture(ROOT / "inputs", name))
result[label] = inventory(root)
return result
if __name__ == "__main__":
actual = demo()
expected = json.loads((ROOT / "expected" / "inventory.json").read_text("utf-8"))
if actual != expected:
raise SystemExit("FAIL: inventory differs from its fixed expected reports")
print(json.dumps(actual, ensure_ascii=False, indent=2))
expected/inventory.json 同时是固定预期和该程序的完整标准输出。misplaced 的第一行报告为 declared_effort: null,顶层只有 projects;repaired 的第一行变为 medium,并多出根部键。其余四行应逐项相同。SHA-256 的变化说明读取了另一份字节内容,不说明这份文件已进入应用的加载链。
expected/inventory.json
{
"misplaced": {
"scope": "fixture files only; no effective config claim",
"files": [
{
"file": "codex-home/config.toml",
"sha256": "46c2fa941f0d5cb29e85e5bb8b22e58cab21e8bbb6821d11673546b1f8b596cf",
"declared_effort": null,
"top_level_keys": [
"projects"
]
},
{
"file": "codex-home/inspect.config.toml",
"sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
"declared_effort": "high",
"top_level_keys": [
"model_reasoning_effort"
]
},
{
"file": "tide-ledger/.codex/config.toml",
"sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
"declared_effort": "high",
"top_level_keys": [
"model_reasoning_effort"
]
},
{
"file": "tide-ledger/app/.codex/config.toml",
"sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
"declared_effort": "high",
"top_level_keys": [
"model_reasoning_effort"
]
},
{
"file": "ridge-notes/.codex/config.toml",
"sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
"declared_effort": "high",
"top_level_keys": [
"model_reasoning_effort"
]
}
]
},
"repaired": {
"scope": "fixture files only; no effective config claim",
"files": [
{
"file": "codex-home/config.toml",
"sha256": "7132b626f37a599bba42fc0dc1d7b9216ed82857ab903dac22dcaf0954c95c76",
"declared_effort": "medium",
"top_level_keys": [
"model_reasoning_effort",
"projects"
]
},
{
"file": "codex-home/inspect.config.toml",
"sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
"declared_effort": "high",
"top_level_keys": [
"model_reasoning_effort"
]
},
{
"file": "tide-ledger/.codex/config.toml",
"sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
"declared_effort": "high",
"top_level_keys": [
"model_reasoning_effort"
]
},
{
"file": "tide-ledger/app/.codex/config.toml",
"sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
"declared_effort": "high",
"top_level_keys": [
"model_reasoning_effort"
]
},
{
"file": "ridge-notes/.codex/config.toml",
"sha256": "5e91aa2760b8c92c04786cfe910c9d555bc5636ffc0300829d894ae98b21420a",
"declared_effort": "high",
"top_level_keys": [
"model_reasoning_effort"
]
}
]
}
}
python3 -B inventory_scope_demo.py
真实排查中,先保留原文件或可恢复版本,在单独副本中确认完整路径与类型,再提出最小修改。若解析报错,先停在编码或语法层;若根部有表而非预期字符串,先核对写法;若根部已经是期望字符串,才继续沿前文的工作目录、profile、项目层和命令行查来源。不要反复追加同一行,也不要把改变 trust 当作修复 TOML 归属的步骤。
本补充核对日期为 2026-10-13,执行范围仅为 Python 3.12.14 的本地合成解析与清单检查。未启动 Codex、未读取或修改真实用户配置、未执行 /debug-config、未对新增反例做 Codex Schema 校验,也未验证客户端会忽略、警告还是拒绝嵌套的同名设置。这些问题仍须在获准的实际环境里独立观察,不能由 TOML 可解析推出答案。
新增程序、夹具和图 4 均为原创;官方资料只用于核对语言及库行为,链接已放在相应说明旁。图 4 以 1120×620 自然尺寸离线渲染核对,未做浏览器页面布局测试。前文三个程序和既有 11 节保持原文,公开包也包含它们,便于读者在同一处保存完整练习。
下载 TOML 作用域补充的完整离线包(SHA-256:1b788a1e77f472c60ae81d19396bce12dd538c031072f2193bc81ba018aaf48b)。包含完整文章、前文三份程序、新探针、八份输入、固定预期、17 项测试、完整输出比较器、错误变体、原清单对照和四幅原创图;不含账户资料、运行缓存或内部工作记录。
16. 来源、许可与复核范围
本文的目录、任务单、记录格式、程序和三幅 SVG 均为原创。官方文档只作事实核对并以链接引用;没有整页搬运。Schema 仅存于私下 QA 目录,不包含在交付 ZIP 中。
- Config basics:普通配置来源与优先级
- Advanced Configuration:独立 profile 与 CODEX_HOME
- Configuration Reference:trust 字段和项目根标记
- Developer commands:/debug-config 的交互入口
- Managed configuration:要求、通知和策略来源
- 固定提交 Schema:离线结构检查依据
- openai/codex:Apache-2.0 许可
滚动文档和本机客户端可能继续变化。复现时先重新核对版本与帮助,再记录你的实际结果。本文没有验证模型可用性、真实项目加载、系统沙箱、网络或组织策略执行效果。