diff --git a/.codex/agents/explorer.toml b/.codex/agents/explorer.toml
index af7c1965..31798d95 100644
--- a/.codex/agents/explorer.toml
+++ b/.codex/agents/explorer.toml
@@ -1,4 +1,4 @@
-model = "o4-mini"
+model = "gpt-5.4"
model_reasoning_effort = "medium"
sandbox_mode = "read-only"
diff --git a/.codex/config.toml b/.codex/config.toml
index c60224ac..3a5dd147 100644
--- a/.codex/config.toml
+++ b/.codex/config.toml
@@ -10,8 +10,9 @@
# - https://developers.openai.com/codex/multi-agent
# Model selection
-model = "o4-mini"
-model_provider = "openai"
+# Leave `model` and `model_provider` unset so Codex CLI uses its current
+# built-in defaults. Uncomment and pin them only if you intentionally want
+# repo-local or global model overrides.
# Top-level runtime settings (current Codex schema)
approval_policy = "on-request"
diff --git a/README.md b/README.md
index 568f069b..9be5f09a 100644
--- a/README.md
+++ b/README.md
@@ -940,6 +940,7 @@ Codex macOS app:
- Open this repository as your workspace.
- The root `AGENTS.md` is auto-detected.
- `.codex/config.toml` and `.codex/agents/*.toml` work best when kept project-local.
+- The reference `.codex/config.toml` intentionally does not pin `model` or `model_provider`, so Codex uses its own current default unless you override it.
- Optional: copy `.codex/config.toml` to `~/.codex/config.toml` for global defaults; keep the multi-agent role files project-local unless you also copy `.codex/agents/`.
### What's Included
diff --git a/docs/ja-JP/skills/django-security/SKILL.md b/docs/ja-JP/skills/django-security/SKILL.md
index 798824ea..55d20725 100644
--- a/docs/ja-JP/skills/django-security/SKILL.md
+++ b/docs/ja-JP/skills/django-security/SKILL.md
@@ -581,7 +581,7 @@ LOGGING = {
| 強力なシークレット | SECRET_KEYに環境変数を使用 |
| パスワード検証 | すべてのパスワードバリデータを有効化 |
| CSRF保護 | デフォルトで有効、無効にしない |
-| XSS防止 | Djangoは自動エスケープ、ユーザー入力で`|safe`を使用しない |
+| XSS防止 | Djangoは自動エスケープ、ユーザー入力で\|safeを使用しない |
| SQLインジェクション | ORMを使用、クエリで文字列を連結しない |
| ファイルアップロード | ファイルタイプとサイズを検証 |
| レート制限 | APIエンドポイントをスロットル |
diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md
index 036b82ca..478d8afc 100644
--- a/docs/zh-CN/README.md
+++ b/docs/zh-CN/README.md
@@ -939,13 +939,14 @@ Codex macOS 应用:
* 将此仓库作为您的工作区打开。
* 根目录的 `AGENTS.md` 会被自动检测。
+* 参考 `.codex/config.toml` 故意不固定 `model` 或 `model_provider`,因此 Codex 会使用它自己的当前默认值,除非您显式覆盖。
* 可选:将 `.codex/config.toml` 复制到 `~/.codex/config.toml` 以实现 CLI/应用行为一致性。
### 包含内容
| 组件 | 数量 | 详情 |
|-----------|-------|---------|
-| 配置 | 1 | `.codex/config.toml` — 模型、权限、MCP 服务器、持久指令 |
+| 配置 | 1 | `.codex/config.toml` — 权限、MCP 服务器、通知和配置文件 |
| AGENTS.md | 2 | 根目录(通用)+ `.codex/AGENTS.md`(Codex 特定补充) |
| 技能 | 16 | `.agents/skills/` — 每个技能包含 SKILL.md + agents/openai.yaml |
| MCP 服务器 | 4 | GitHub、Context7、Memory、Sequential Thinking(基于命令) |
diff --git a/docs/zh-TW/agents/database-reviewer.md b/docs/zh-TW/agents/database-reviewer.md
index 7c328feb..968aa47b 100644
--- a/docs/zh-TW/agents/database-reviewer.md
+++ b/docs/zh-TW/agents/database-reviewer.md
@@ -129,7 +129,7 @@ CREATE INDEX orders_customer_id_idx ON orders (customer_id);
| 索引類型 | 使用場景 | 運算子 |
|----------|----------|--------|
| **B-tree**(預設)| 等於、範圍 | `=`、`<`、`>`、`BETWEEN`、`IN` |
-| **GIN** | 陣列、JSONB、全文搜尋 | `@>`、`?`、`?&`、`?|`、`@@` |
+| **GIN** | 陣列、JSONB、全文搜尋 | `@>`、`?`、`?&`、?\|、`@@` |
| **BRIN** | 大型時序表 | 排序資料的範圍查詢 |
| **Hash** | 僅等於 | `=`(比 B-tree 略快)|
diff --git a/skills/videodb/SKILL.md b/skills/videodb/SKILL.md
index 1e6e4403..fe3216d7 100644
--- a/skills/videodb/SKILL.md
+++ b/skills/videodb/SKILL.md
@@ -108,7 +108,7 @@ The user must set `VIDEO_DB_API_KEY` using **either** method:
- **Export in terminal** (before starting Claude): `export VIDEO_DB_API_KEY=your-key`
- **Project `.env` file**: Save `VIDEO_DB_API_KEY=your-key` in the project's `.env` file
-Get a free API key at (50 free uploads, no credit card).
+Get a free API key at [console.videodb.io](https://console.videodb.io) (50 free uploads, no credit card).
**Do NOT** read, write, or handle the API key yourself. Always let the user set it.
diff --git a/skills/videodb/reference/capture-reference.md b/skills/videodb/reference/capture-reference.md
index 7dc98f60..b002c493 100644
--- a/skills/videodb/reference/capture-reference.md
+++ b/skills/videodb/reference/capture-reference.md
@@ -107,7 +107,7 @@ Use [scripts/ws_listener.py](../scripts/ws_listener.py) to connect and dump even
}
```
-> For latest details, see
+> For latest details, see [the realtime context docs](https://docs.videodb.io/pages/ingest/capture-sdks/realtime-context.md).
---
diff --git a/tests/codex-config.test.js b/tests/codex-config.test.js
new file mode 100644
index 00000000..8cff7983
--- /dev/null
+++ b/tests/codex-config.test.js
@@ -0,0 +1,70 @@
+/**
+ * Tests for `.codex/config.toml` reference defaults.
+ *
+ * Run with: node tests/codex-config.test.js
+ */
+
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+
+function test(name, fn) {
+ try {
+ fn();
+ console.log(` ✓ ${name}`);
+ return true;
+ } catch (err) {
+ console.log(` ✗ ${name}`);
+ console.log(` Error: ${err.message}`);
+ return false;
+ }
+}
+
+const repoRoot = path.join(__dirname, '..');
+const configPath = path.join(repoRoot, '.codex', 'config.toml');
+const config = fs.readFileSync(configPath, 'utf8');
+const codexAgentsDir = path.join(repoRoot, '.codex', 'agents');
+
+let passed = 0;
+let failed = 0;
+
+if (
+ test('reference config does not pin a top-level model', () => {
+ assert.ok(!/^model\s*=/m.test(config), 'Expected `.codex/config.toml` to inherit the CLI default model');
+ })
+)
+ passed++;
+else failed++;
+
+if (
+ test('reference config does not pin a top-level model provider', () => {
+ assert.ok(
+ !/^model_provider\s*=/m.test(config),
+ 'Expected `.codex/config.toml` to inherit the CLI default provider',
+ );
+ })
+)
+ passed++;
+else failed++;
+
+if (
+ test('sample Codex role configs do not use o4-mini', () => {
+ const roleFiles = fs.readdirSync(codexAgentsDir).filter(file => file.endsWith('.toml'));
+ assert.ok(roleFiles.length > 0, 'Expected sample role config files under `.codex/agents`');
+
+ for (const roleFile of roleFiles) {
+ const rolePath = path.join(codexAgentsDir, roleFile);
+ const roleConfig = fs.readFileSync(rolePath, 'utf8');
+ assert.ok(
+ !/^model\s*=\s*"o4-mini"$/m.test(roleConfig),
+ `Expected sample role config to avoid o4-mini: ${roleFile}`,
+ );
+ }
+ })
+)
+ passed++;
+else failed++;
+
+console.log(`\nPassed: ${passed}`);
+console.log(`Failed: ${failed}`);
+process.exit(failed > 0 ? 1 : 0);
diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js
index 0352c415..57c19c56 100644
--- a/tests/hooks/hooks.test.js
+++ b/tests/hooks/hooks.test.js
@@ -98,6 +98,44 @@ function cleanupTestDir(testDir) {
fs.rmSync(testDir, { recursive: true, force: true });
}
+function normalizeComparablePath(targetPath) {
+ if (!targetPath) return '';
+
+ let normalizedPath = String(targetPath).trim().replace(/\\/g, '/');
+
+ if (/^\/[a-zA-Z]\//.test(normalizedPath)) {
+ normalizedPath = `${normalizedPath[1]}:/${normalizedPath.slice(3)}`;
+ }
+
+ if (/^[a-zA-Z]:\//.test(normalizedPath)) {
+ normalizedPath = `${normalizedPath[0].toUpperCase()}:${normalizedPath.slice(2)}`;
+ }
+
+ try {
+ normalizedPath = fs.realpathSync(normalizedPath);
+ } catch {
+ // Fall through to string normalization when the path cannot be resolved directly.
+ }
+
+ return path.normalize(normalizedPath).replace(/\\/g, '/').replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
+}
+
+function pathsReferToSameLocation(leftPath, rightPath) {
+ const normalizedLeftPath = normalizeComparablePath(leftPath);
+ const normalizedRightPath = normalizeComparablePath(rightPath);
+
+ if (!normalizedLeftPath || !normalizedRightPath) return false;
+ if (normalizedLeftPath === normalizedRightPath) return true;
+
+ try {
+ const leftStats = fs.statSync(normalizedLeftPath);
+ const rightStats = fs.statSync(normalizedRightPath);
+ return leftStats.dev === rightStats.dev && leftStats.ino === rightStats.ino;
+ } catch {
+ return false;
+ }
+}
+
function createCommandShim(binDir, baseName, logFile) {
fs.mkdirSync(binDir, { recursive: true });
@@ -155,6 +193,7 @@ async function runTests() {
let passed = 0;
let failed = 0;
+ let skipped = 0;
const scriptsDir = path.join(__dirname, '..', '..', 'scripts', 'hooks');
@@ -2148,12 +2187,12 @@ async function runTests() {
passed++;
else failed++;
- if (
+ if (process.platform === 'win32') {
+ console.log(' - detect-project writes project metadata to the registry and project directory');
+ console.log(' (skipped — bash script paths are not Windows-compatible)');
+ skipped++;
+ } else if (
await asyncTest('detect-project writes project metadata to the registry and project directory', async () => {
- if (process.platform === 'win32') {
- console.log(' (skipped — bash script paths are not Windows-compatible)');
- return true;
- }
const testRoot = createTestDir();
const homeDir = path.join(testRoot, 'home');
const repoDir = path.join(testRoot, 'repo');
@@ -2189,9 +2228,9 @@ async function runTests() {
assert.strictEqual(code, 0, `detect-project should source cleanly, stderr: ${stderr}`);
- const [projectId, projectDir] = stdout.trim().split(/\r?\n/);
+ const [projectId] = stdout.trim().split(/\r?\n/);
const registryPath = path.join(homeDir, '.claude', 'homunculus', 'projects.json');
- const projectMetadataPath = path.join(projectDir, 'project.json');
+ const projectMetadataPath = path.join(homeDir, '.claude', 'homunculus', 'projects', projectId, 'project.json');
assert.ok(projectId, 'detect-project should emit a project id');
assert.ok(fs.existsSync(registryPath), 'projects.json should be created');
@@ -2203,7 +2242,13 @@ async function runTests() {
assert.ok(registry[projectId], 'registry should contain the detected project');
assert.strictEqual(metadata.id, projectId, 'project.json should include the detected id');
assert.strictEqual(metadata.name, path.basename(repoDir), 'project.json should include the repo name');
- assert.strictEqual(fs.realpathSync(metadata.root), fs.realpathSync(repoDir), 'project.json should include the repo root');
+ const normalizedMetadataRoot = normalizeComparablePath(metadata.root);
+ const normalizedRepoDir = normalizeComparablePath(repoDir);
+ assert.ok(normalizedMetadataRoot, 'project.json should include a non-empty repo root');
+ assert.ok(
+ pathsReferToSameLocation(normalizedMetadataRoot, normalizedRepoDir),
+ `project.json should include the repo root (expected ${normalizedRepoDir}, got ${normalizedMetadataRoot})`,
+ );
assert.strictEqual(metadata.remote, 'https://github.com/example/ecc-test.git', 'project.json should include the sanitized remote');
assert.ok(metadata.created_at, 'project.json should include created_at');
assert.ok(metadata.last_seen, 'project.json should include last_seen');
@@ -4242,12 +4287,12 @@ async function runTests() {
// ── Round 74: session-start.js main().catch handler ──
console.log('\nRound 74: session-start.js (main catch — unrecoverable error):');
- if (
+ if (process.platform === 'win32') {
+ console.log(' - session-start exits 0 with error message when HOME is non-directory');
+ console.log(' (skipped — /dev/null not available on Windows)');
+ skipped++;
+ } else if (
await asyncTest('session-start exits 0 with error message when HOME is non-directory', async () => {
- if (process.platform === 'win32') {
- console.log(' (skipped — /dev/null not available on Windows)');
- return;
- }
// HOME=/dev/null makes ensureDir(sessionsDir) throw ENOTDIR,
// which propagates to main().catch — the top-level error boundary
const result = await runScript(path.join(scriptsDir, 'session-start.js'), '', {
@@ -4264,12 +4309,12 @@ async function runTests() {
// ── Round 75: pre-compact.js main().catch handler ──
console.log('\nRound 75: pre-compact.js (main catch — unrecoverable error):');
- if (
+ if (process.platform === 'win32') {
+ console.log(' - pre-compact exits 0 with error message when HOME is non-directory');
+ console.log(' (skipped — /dev/null not available on Windows)');
+ skipped++;
+ } else if (
await asyncTest('pre-compact exits 0 with error message when HOME is non-directory', async () => {
- if (process.platform === 'win32') {
- console.log(' (skipped — /dev/null not available on Windows)');
- return;
- }
// HOME=/dev/null makes ensureDir(sessionsDir) throw ENOTDIR,
// which propagates to main().catch — the top-level error boundary
const result = await runScript(path.join(scriptsDir, 'pre-compact.js'), '', {
@@ -4286,12 +4331,12 @@ async function runTests() {
// ── Round 75: session-end.js main().catch handler ──
console.log('\nRound 75: session-end.js (main catch — unrecoverable error):');
- if (
+ if (process.platform === 'win32') {
+ console.log(' - session-end exits 0 with error message when HOME is non-directory');
+ console.log(' (skipped — /dev/null not available on Windows)');
+ skipped++;
+ } else if (
await asyncTest('session-end exits 0 with error message when HOME is non-directory', async () => {
- if (process.platform === 'win32') {
- console.log(' (skipped — /dev/null not available on Windows)');
- return;
- }
// HOME=/dev/null makes ensureDir(sessionsDir) throw ENOTDIR inside main(),
// which propagates to runMain().catch — the top-level error boundary
const result = await runScript(path.join(scriptsDir, 'session-end.js'), '{}', {
@@ -4308,12 +4353,12 @@ async function runTests() {
// ── Round 76: evaluate-session.js main().catch handler ──
console.log('\nRound 76: evaluate-session.js (main catch — unrecoverable error):');
- if (
+ if (process.platform === 'win32') {
+ console.log(' - evaluate-session exits 0 with error message when HOME is non-directory');
+ console.log(' (skipped — /dev/null not available on Windows)');
+ skipped++;
+ } else if (
await asyncTest('evaluate-session exits 0 with error message when HOME is non-directory', async () => {
- if (process.platform === 'win32') {
- console.log(' (skipped — /dev/null not available on Windows)');
- return;
- }
// HOME=/dev/null makes ensureDir(learnedSkillsPath) throw ENOTDIR,
// which propagates to main().catch — the top-level error boundary
const result = await runScript(path.join(scriptsDir, 'evaluate-session.js'), '{}', {
@@ -4330,12 +4375,12 @@ async function runTests() {
// ── Round 76: suggest-compact.js main().catch handler ──
console.log('\nRound 76: suggest-compact.js (main catch — double-failure):');
- if (
+ if (process.platform === 'win32') {
+ console.log(' - suggest-compact exits 0 with error when TMPDIR is non-directory');
+ console.log(' (skipped — /dev/null not available on Windows)');
+ skipped++;
+ } else if (
await asyncTest('suggest-compact exits 0 with error when TMPDIR is non-directory', async () => {
- if (process.platform === 'win32') {
- console.log(' (skipped — /dev/null not available on Windows)');
- return;
- }
// TMPDIR=/dev/null causes openSync to fail (ENOTDIR), then the catch
// fallback writeFile also fails, propagating to main().catch
const result = await runScript(path.join(scriptsDir, 'suggest-compact.js'), '', {
@@ -4807,7 +4852,8 @@ Some random content without the expected ### Context to Load section
console.log('\n=== Test Results ===');
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
- console.log(`Total: ${passed + failed}\n`);
+ console.log(`Skipped: ${skipped}`);
+ console.log(`Total: ${passed + failed + skipped}\n`);
process.exit(failed > 0 ? 1 : 0);
}