#!/usr/bin/env bash
set -euo pipefail
PASSED=0
FAILED=0
log_test() { echo "[TEST] $1"; }
log_pass() { echo "[PASS] $1"; PASSED=$((PASSED + 1)); }
log_fail() { echo "[FAIL] $1"; FAILED=$((FAILED + 1)); }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$PROJECT_ROOT"
log_test "Required files exist"
required_files=(
"codex/AGENTS.md"
"codex/README.md"
"codex/.codex/rules/harness.rules"
".codex-plugin/plugin.json"
)
all_exist=true
for file in "${required_files[@]}"; do
if [ -f "$file" ]; then
echo " ok: $file"
else
echo " missing: $file"
all_exist=false
fi
done
if $all_exist; then
log_pass "Required files present"
else
log_fail "Missing required files"
fi
log_test "Execpolicy rules examples are valid"
if command -v python3 >/dev/null 2>&1; then
if python3 - <<'PY'
from __future__ import annotations
import shlex
import sys
from pathlib import Path
def _matches_prefix(pattern: list[object], tokens: list[str]) -> bool:
if len(tokens) < len(pattern):
return False
for i, pe in enumerate(pattern):
t = tokens[i]
if isinstance(pe, str):
if t != pe:
return False
elif isinstance(pe, (list, tuple)):
if t not in pe:
return False
else:
raise TypeError(f"Unsupported pattern element at index {i}: {pe!r}")
return True
def _load_rules(path: Path) -> list[dict[str, object]]:
rules: list[dict[str, object]] = []
def prefix_rule(**kwargs):
rules.append(kwargs)
g = {"prefix_rule": prefix_rule}
code = path.read_text(encoding="utf-8")
exec(compile(code, str(path), "exec"), g, {})
return rules
def _validate(path: Path) -> list[str]:
errs: list[str] = []
rules = _load_rules(path)
if not rules:
return [f"{path}: no prefix_rule() found"]
for idx, rule in enumerate(rules):
pattern = rule.get("pattern")
if not isinstance(pattern, list):
errs.append(f"{path}: rule {idx} missing/invalid pattern: {pattern!r}")
continue
for field, should_match in (("match", True), ("not_match", False)):
examples = rule.get(field, [])
if examples is None:
continue
if not isinstance(examples, list):
errs.append(f"{path}: rule {idx} {field} is not a list: {examples!r}")
continue
for ex in examples:
if not isinstance(ex, str):
errs.append(f"{path}: rule {idx} {field} example is not str: {ex!r}")
continue
tokens = shlex.split(ex)
ok = _matches_prefix(pattern, tokens)
if ok != should_match:
verdict = "matches" if ok else "does not match"
errs.append(
f"{path}: rule {idx} {field} example {ex!r} {verdict} pattern {pattern!r}"
)
return errs
errors: list[str] = []
for p in [Path("codex/.codex/rules/harness.rules")]:
errors.extend(_validate(p))
if errors:
print("ERROR: execpolicy rules examples invalid:")
for e in errors:
print(" -", e)
sys.exit(1)
print("ok")
PY
then
log_pass "Rules examples are consistent"
else
log_fail "Rules examples invalid (Codex may ignore custom rules)"
fi
else
echo " skipped: python3 not found"
log_pass "Rules examples check skipped"
fi
log_test "Codex path-based core skills exist"
required_skill_dirs=(
"codex/.codex/skills/harness-plan"
"codex/.codex/skills/harness-sync"
"codex/.codex/skills/harness-work"
"codex/.codex/skills/harness-review"
"codex/.codex/skills/harness-release"
"codex/.codex/skills/harness-setup"
"codex/.codex/skills/breezing"
"codex/.codex/skills/harness-loop"
)
skills_ok=true
for dir in "${required_skill_dirs[@]}"; do
if [ -d "$dir" ] && [ ! -L "$dir" ]; then
echo " ok: $dir"
else
if [ -L "$dir" ]; then
echo " symlink: $dir"
else
echo " missing: $dir"
fi
skills_ok=false
fi
done
if $skills_ok; then
log_pass "Path-based core skills are present"
else
log_fail "Missing path-based core skills"
fi
log_test "Codex host package ships helpers for advertised Cursor skills"
codex_dist_ok=true
codex_dist_tmp="$(mktemp -d)"
if bash scripts/build-host-plugin-dist.sh --host codex --out "${codex_dist_tmp}" >/tmp/codex-host-dist.$$ 2>&1; then
required_codex_dist_paths=(
".codex-plugin/plugin.json"
"cursor-skills/breezing/SKILL.md"
"cursor-skills/harness-work/SKILL.md"
"skills/cursor-do/SKILL.md"
"skills/cursor-ask/SKILL.md"
"skills/cursor-review/SKILL.md"
"skills/cursor-setup/SKILL.md"
"scripts/build-host-plugin-dist.sh"
"scripts/codex-companion.sh"
"scripts/codex-primary-environment-guard.sh"
"scripts/cursor-companion.sh"
"scripts/model-routing.sh"
"scripts/resolve-impl-backend.sh"
"scripts/set-impl-backend.sh"
"scripts/setup-cursor.sh"
)
for path in "${required_codex_dist_paths[@]}"; do
if [ ! -e "${codex_dist_tmp}/${path}" ]; then
echo " missing from generated Codex package: ${path}"
codex_dist_ok=false
fi
done
if [ -e "${codex_dist_tmp}/.cursor-plugin" ] || [ -e "${codex_dist_tmp}/.cursor" ]; then
echo " generated Codex package must not expose Cursor manifests or .cursor bootstrap files"
codex_dist_ok=false
fi
if ! HARNESS_PROJECT_ROOT="${codex_dist_tmp}" HARNESS_CURSOR_DIST="${codex_dist_tmp}/cursor-check" \
bash "${codex_dist_tmp}/scripts/setup-cursor.sh" --check >/tmp/codex-cursor-setup-check.$$ 2>&1; then
echo " setup-cursor --check failed inside generated Codex package"
sed 's/^/ /' /tmp/codex-cursor-setup-check.$$ | head -80
codex_dist_ok=false
fi
if rg -q --fixed-strings 'Codex host 版' "${codex_dist_tmp}/cursor-check/skills/breezing/SKILL.md" 2>/dev/null; then
echo " generated Cursor package used Codex-host breezing skill source"
codex_dist_ok=false
fi
if ! rg -q --fixed-strings 'allowed-tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob", "Task", "WebSearch", "Monitor"]' \
"${codex_dist_tmp}/cursor-check/skills/breezing/SKILL.md" 2>/dev/null; then
echo " generated Cursor package did not use canonical shared breezing skill source"
codex_dist_ok=false
fi
required_cursor_dist_paths=(
".cursor-plugin/plugin.json"
"skills/cursor-do/SKILL.md"
"scripts/build-host-plugin-dist.sh"
"scripts/cursor-companion.sh"
"scripts/model-routing.sh"
"scripts/resolve-impl-backend.sh"
"scripts/set-impl-backend.sh"
"scripts/setup-cursor.sh"
)
for path in "${required_cursor_dist_paths[@]}"; do
if [ ! -e "${codex_dist_tmp}/cursor-check/${path}" ]; then
echo " missing from generated Cursor package: ${path}"
codex_dist_ok=false
fi
done
else
sed 's/^/ /' /tmp/codex-host-dist.$$
codex_dist_ok=false
fi
rm -rf "${codex_dist_tmp}" /tmp/codex-host-dist.$$ /tmp/codex-cursor-setup-check.$$ 2>/dev/null || true
if $codex_dist_ok; then
log_pass "Codex host package contains runtime helpers required by Cursor skills"
else
log_fail "Codex host package is missing runtime helpers for Cursor skills"
fi
log_test "Shipped Codex/OpenCode skills have required description frontmatter"
skill_frontmatter_ok=true
while IFS= read -r skill_file; do
if ! rg -q '^description:' "$skill_file"; then
echo " missing description: $skill_file"
skill_frontmatter_ok=false
fi
done < <(find codex/.codex/skills opencode/skills -name SKILL.md | sort)
if $skill_frontmatter_ok; then
log_pass "All shipped skill bundles have description frontmatter"
else
log_fail "Some shipped skill bundles are invalid for Codex skill loading"
fi
log_test "Public skill mirrors stay in sync"
if ./scripts/sync-skill-mirrors.sh --check >/tmp/codex-skill-mirrors.$$ 2>&1; then
log_pass "Public skill mirrors match skills"
else
cat /tmp/codex-skill-mirrors.$$ | sed 's/^/ /'
log_fail "Public skill mirrors drifted from skills"
fi
rm -f /tmp/codex-skill-mirrors.$$ || true
log_test "Codex harness-plan ships co-required spec output contract"
codex_harness_plan_contract_ok=true
for required_contract_term in \
'co-required planning output' \
'spec.md product contract and Plans.md task contract' \
'spec.md > sub-spec > Plans.md' \
'Spec delta' \
'Spec skip reason' \
'Harness が生成し、consumer は承認・修正だけ'; do
if ! rg -q --fixed-strings "$required_contract_term" \
"codex/.codex/skills/harness-plan/SKILL.md" \
"codex/.codex/skills/harness-plan/references/create.md" \
"codex/.codex/skills/harness-plan/references/planning-quality.md"; then
echo " missing harness-plan contract term: $required_contract_term"
codex_harness_plan_contract_ok=false
fi
done
if $codex_harness_plan_contract_ok; then
log_pass "Codex harness-plan includes co-required spec output contract"
else
log_fail "Codex harness-plan spec output contract is incomplete"
fi
log_test "Non-breezing Codex skills are CLI-only"
cli_only_targets=(
"codex/.codex/skills/harness-work/SKILL.md"
"codex/.codex/skills/harness-review/SKILL.md"
"codex/.codex/skills/routing-rules.md"
)
forbidden_cli_terms=(
"Codex MCP"
"claude mcp add --scope user codex"
"claude mcp list | grep -i codex"
"@openai/codex-cli"
"MCP server connection error"
"all MCP calls"
)
cli_only_ok=true
for pat in "${forbidden_cli_terms[@]}"; do
if rg -n --fixed-strings "$pat" "${cli_only_targets[@]}" >/tmp/codex-cli-only.$$ 2>/dev/null; then
echo " forbidden CLI-only pattern found: $pat"
head -5 /tmp/codex-cli-only.$$ | sed 's/^/ /'
cli_only_ok=false
fi
done
rm -f /tmp/codex-cli-only.$$ || true
if $cli_only_ok; then
log_pass "CLI-only vocabulary checks passed for non-breezing Codex skills"
else
log_fail "CLI-only vocabulary check failed for non-breezing Codex skills"
fi
log_test "Codex docs point at harness-* workflow surfaces"
workflow_surface_ok=true
for required_surface in '$harness-plan' '$harness-sync' '$harness-work' '$breezing' '$harness-review' '$harness-loop'; do
if ! rg -q --fixed-strings "$required_surface" "codex/README.md" "codex/AGENTS.md"; then
echo " missing: ${required_surface} in codex docs"
workflow_surface_ok=false
fi
done
if ! rg -q --fixed-strings '$harness-work' "codex/README.md"; then
echo " missing: \$harness-work in codex/README.md"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings '$harness-review' "codex/README.md"; then
echo " missing: \$harness-review in codex/README.md"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings 'harness codex-loop start/status/stop' "codex/README.md" "codex/.codex/skills/harness-loop/SKILL.md"; then
echo " missing: codex-loop runtime note"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings 'Realtime Handoff / Silence Policy' "codex/.codex/skills/harness-loop/SKILL.md" "codex/.codex/skills/breezing/SKILL.md"; then
echo " missing: realtime handoff silence policy in Codex skills"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings 'Advisor / Reviewer drift' "codex/.codex/skills/harness-loop/SKILL.md"; then
echo " missing: Advisor / Reviewer drift exception in harness-loop silence policy"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings 'Realtime Handoff And Silence Policy' "codex/README.md"; then
echo " missing: realtime handoff silence policy in codex/README.md"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings 'spawn_agent' "codex/README.md" "codex/.codex/skills/breezing/SKILL.md"; then
echo " missing: spawn_agent native orchestration note"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings '| `--max-workers N` | ready task の同時 spawn 数上限' "codex/.codex/skills/breezing/SKILL.md"; then
echo " missing: breezing default max-workers guidance"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings 'breezing --max-workers 1 all' "codex/.codex/skills/breezing/SKILL.md"; then
echo " missing: breezing serial escape hatch"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings 'ready batch' "codex/README.md" "codex/.codex/skills/harness-loop/SKILL.md"; then
echo " missing: harness-loop ready batch guidance"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings -- '--executor task' "codex/README.md" "codex/.codex/skills/harness-loop/SKILL.md"; then
echo " missing: harness-loop task executor escape hatch"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings -- '--codex' "codex/.codex/skills/harness-work/SKILL.md"; then
echo " missing: --codex in harness-work/SKILL.md"
workflow_surface_ok=false
fi
if ! rg -q --fixed-strings 'harness-work' "codex/.codex/skills/breezing/SKILL.md"; then
echo " missing: harness-work alias note in breezing/SKILL.md"
workflow_surface_ok=false
fi
if [ -L "codex/.codex/skills/breezing" ]; then
echo " symlink: codex/.codex/skills/breezing must be a real directory"
workflow_surface_ok=false
fi
codex_breezing_ssot="skills/breezing"
if [ -d "skills-codex/breezing" ]; then
codex_breezing_ssot="skills-codex/breezing"
fi
if ! diff -qr "${codex_breezing_ssot}" "codex/.codex/skills/breezing" >/dev/null 2>&1; then
echo " drift: codex breezing mirror does not match ${codex_breezing_ssot}"
workflow_surface_ok=false
fi
for forbidden_pat in '$plan-with-agent' '$work' '$verify' '$remember'; do
if rg -n --fixed-strings "$forbidden_pat" "codex/AGENTS.md" >/tmp/codex-surface-forbidden.$$ 2>/dev/null; then
echo " forbidden legacy command remains: $forbidden_pat"
head -5 /tmp/codex-surface-forbidden.$$ | sed 's/^/ /'
workflow_surface_ok=false
fi
done
rm -f /tmp/codex-surface-forbidden.$$ || true
if $workflow_surface_ok; then
log_pass "Harness workflow surfaces are documented for Codex"
else
log_fail "Harness workflow surface checks failed"
fi
log_test "codex/.codex/config.toml has multi_agent + harness roles"
config_ok=true
if ! rg -q --fixed-strings "multi_agent = true" "codex/.codex/config.toml"; then
echo " missing: multi_agent = true"
config_ok=false
fi
for role in "implementer" "reviewer" "claude_implementer" "claude_reviewer"; do
if ! rg -q --fixed-strings "[agents.${role}]" "codex/.codex/config.toml"; then
echo " missing: [agents.${role}]"
config_ok=false
fi
done
if $config_ok; then
log_pass "config.toml has required multi-agent defaults"
else
log_fail "config.toml missing required multi-agent defaults"
fi
log_test "Codex setup scripts guard against duplicate skill listings"
scripts_ok=true
setup_scripts=(
"scripts/setup-codex.sh"
"scripts/codex-setup-local.sh"
)
for script in "${setup_scripts[@]}"; do
if rg -q --fixed-strings '${target}.backup.' "$script"; then
echo " legacy in-place backup naming remains: $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings 'should_skip_sync_entry' "$script"; then
echo " missing should_skip_sync_entry: $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings 'cleanup_legacy_skill_entries' "$script"; then
echo " missing cleanup_legacy_skill_entries: $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings 'extract_skill_frontmatter_name' "$script"; then
echo " missing extract_skill_frontmatter_name: $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings 'cleanup_legacy_skill_name_duplicates' "$script"; then
echo " missing cleanup_legacy_skill_name_duplicates: $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings 'is_legacy_harness_skill_name' "$script"; then
echo " missing is_legacy_harness_skill_name: $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings 'is_harness_managed_skill_entry' "$script"; then
echo " missing is_harness_managed_skill_entry: $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings 'cleanup_removed_harness_skill_entries' "$script"; then
echo " missing cleanup_removed_harness_skill_entries: $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings '_archived|*.backup.*' "$script"; then
echo " missing legacy skip rule (_archived|*.backup.*): $script"
scripts_ok=false
fi
if ! rg -q --fixed-strings '/backups/' "$script"; then
echo " missing external backup root (/backups/): $script"
scripts_ok=false
fi
done
if $scripts_ok; then
log_pass "Setup script duplicate-skill guards are present"
else
log_fail "Setup script duplicate-skill guards are missing"
fi
log_test "codex-setup-local handles symlinked skill installs safely"
if bash tests/test-codex-setup-local.sh >/tmp/codex-setup-symlink.$$ 2>&1; then
log_pass "Symlinked skill installs are safe"
else
cat /tmp/codex-setup-symlink.$$ | sed 's/^/ /'
log_fail "Symlinked skill install safety check failed"
fi
rm -f /tmp/codex-setup-symlink.$$ || true
log_test "setup-codex.sh avoids stale notify config"
if rg -q '^\[notify\]' "scripts/setup-codex.sh"; then
echo " stale [notify] section remains in scripts/setup-codex.sh"
log_fail "setup-codex.sh still injects invalid notify config"
else
log_pass "setup-codex.sh does not inject stale notify config"
fi
log_test "codex README documents the reliable user update path"
readme_update_path_ok=true
if ! rg -q --fixed-strings 'Option 1: Script (recommended, user-based)' "codex/README.md"; then
echo " missing recommended script wording"
readme_update_path_ok=false
fi
if ! rg -q --fixed-strings 'rerun the same script to sync `~/.codex/skills`' "codex/README.md"; then
echo " missing rerun update guidance"
readme_update_path_ok=false
fi
if $readme_update_path_ok; then
log_pass "codex README points users to the reliable update path"
else
log_fail "codex README update-path guidance is incomplete"
fi
log_test "Codex 0.130.0 stable provider and workflow policy is documented"
codex_0130_policy_ok=true
for required_policy_term in \
'Codex `0.130.0` stable (`rust-v0.130.0`, published `2026-05-08T23:09:55Z`)' \
'AWS console-login credentials from `aws login` profiles' \
'Harness does not write AWS credentials' \
'model_provider = "amazon-bedrock"' \
'codex remote-control' \
'page large threads' \
'selected environments' \
'live app-server threads pick up config changes without restart' \
'Turn diffs stay accurate across `apply_patch`, including partial failures' \
'plugin details now show bundled hooks' \
'link metadata and discoverability controls' \
'Configurable OpenTelemetry trace metadata' \
'Built-in MCPs' \
'`CODEX_HOME` environments TOML provider' \
'extra skills list roots'; do
if ! rg -q --fixed-strings "$required_policy_term" "codex/README.md" "docs/codex-provider-setup-policy.md" "docs/codex-plugin-workflows-policy.md" "skills/harness-setup/SKILL.md"; then
echo " missing Codex 0.130.0 policy term: $required_policy_term"
codex_0130_policy_ok=false
fi
done
for required_config_term in \
'policy aligned through 0.130.0 stable' \
'bundled hooks opt-in' \
'does not enable remote-control defaults' \
'AWS console-login credentials from `aws login` profiles' \
'never writes AWS credential material' \
'configurable OpenTelemetry trace metadata' \
'built-in MCPs as first-class runtime servers' \
'CODEX_HOME environments' \
'one primary environment'; do
if ! rg -q --fixed-strings "$required_config_term" "codex/.codex/config.toml"; then
echo " missing Codex 0.130.0 config term: $required_config_term"
codex_0130_policy_ok=false
fi
done
if rg -q '^[[:space:]]*remote[-_]control[[:space:]=]' "codex/.codex/config.toml"; then
echo " config.toml appears to set a remote-control default"
codex_0130_policy_ok=false
fi
if rg -q '^[[:space:]]*hooks[[:space:]=]' "codex/.codex/config.toml"; then
echo " config.toml appears to set inline hooks"
codex_0130_policy_ok=false
fi
if $codex_0130_policy_ok; then
log_pass "Codex 0.130.0 provider and workflow policy is documented"
else
log_fail "Codex 0.130.0 provider/workflow policy checks failed"
fi
log_test "Phase 80 Codex 0.131-0.134 policy is documented"
phase80_codex_ok=true
for required_phase80_term in \
'rust-v0.134.0' \
'primary selector' \
'install.sh' \
'on-failure' \
'extended through 0.134.0'; do
if ! rg -q --fixed-strings "$required_phase80_term" \
"docs/upstream-update-snapshot-2026-05-27.md" \
"docs/codex-permission-profiles-policy.md" \
"codex/AGENTS.md" \
"scripts/setup-codex.sh" \
"codex/.codex/config.toml"; then
echo " missing Phase 80 Codex term: $required_phase80_term"
phase80_codex_ok=false
fi
done
if $phase80_codex_ok; then
log_pass "Phase 80 Codex 0.131-0.134 policy is documented"
else
log_fail "Phase 80 Codex policy checks failed"
fi
log_test "codex README documents MCP verbose diagnostics and .mcp.json loading"
readme_mcp_ok=true
if ! rg -q --fixed-strings 'Codex `0.123.0` keeps the normal `/mcp` view fast' "codex/README.md"; then
echo " missing: plain /mcp fast-path guidance"
readme_mcp_ok=false
fi
if ! rg -q --fixed-strings '/mcp verbose' "codex/README.md"; then
echo " missing: /mcp verbose guidance"
readme_mcp_ok=false
fi
if ! rg -q --fixed-strings 'diagnostics, resources, and resource templates' "codex/README.md"; then
echo " missing: diagnostics/resources/resource templates guidance"
readme_mcp_ok=false
fi
if ! rg -q --fixed-strings '"mcpServers"' "codex/README.md"; then
echo " missing: mcpServers .mcp.json example"
readme_mcp_ok=false
fi
if ! rg -q --fixed-strings 'top-level server map' "codex/README.md"; then
echo " missing: top-level server map loading guidance"
readme_mcp_ok=false
fi
if ! rg -q --fixed-strings 'not Claude Code `claude mcp` or `.claude/mcp.json` guidance' "codex/README.md"; then
echo " missing: Codex vs Claude Code MCP terminology boundary"
readme_mcp_ok=false
fi
if $readme_mcp_ok; then
log_pass "codex README documents MCP diagnostics and plugin loading"
else
log_fail "codex README MCP guidance is incomplete"
fi
log_test "codex README documents sandbox and exec policy"
readme_sandbox_ok=true
if ! rg -q --fixed-strings 'Codex `0.123.0` adds host-specific `remote_sandbox_config` requirements' "codex/README.md"; then
echo " missing: remote_sandbox_config guidance"
readme_sandbox_ok=false
fi
if ! rg -q --fixed-strings 'requirements.toml' "codex/README.md"; then
echo " missing: requirements.toml policy placement"
readme_sandbox_ok=false
fi
if ! rg -q --fixed-strings 'hostname_patterns' "codex/README.md"; then
echo " missing: hostname_patterns example"
readme_sandbox_ok=false
fi
if ! rg -q --fixed-strings 'allowed_sandbox_modes' "codex/README.md"; then
echo " missing: allowed_sandbox_modes example"
readme_sandbox_ok=false
fi
if ! rg -q --fixed-strings 'root-level shared flags' "codex/README.md"; then
echo " missing: codex exec shared flags inheritance guidance"
readme_sandbox_ok=false
fi
if ! rg -q --fixed-strings 'duplicate `--approval-policy` / `--sandbox` pairs' "codex/README.md"; then
echo " missing: duplicate approval/sandbox flag guidance"
readme_sandbox_ok=false
fi
if ! rg -q --fixed-strings 'docs/codex-sandbox-execution-policy.md' "codex/README.md"; then
echo " missing: sandbox execution policy docs pointer"
readme_sandbox_ok=false
fi
if $readme_sandbox_ok; then
log_pass "codex README documents sandbox and exec policy"
else
log_fail "codex README sandbox/exec guidance is incomplete"
fi
log_test "Codex permission profile policy is documented"
permission_policy_ok=true
if [ ! -f "docs/codex-permission-profiles-policy.md" ]; then
echo " missing: docs/codex-permission-profiles-policy.md"
permission_policy_ok=false
fi
if ! rg -q --fixed-strings 'Codex `0.125.0` carries permission profile state' "codex/README.md"; then
echo " missing: Codex 0.125 permission profile README guidance"
permission_policy_ok=false
fi
if ! rg -q --fixed-strings 'Codex `0.128.0` expands this with built-in permission profiles' "codex/README.md"; then
echo " missing: Codex 0.128 permission profile README guidance"
permission_policy_ok=false
fi
if ! rg -q --fixed-strings 'Details: `docs/codex-permission-profiles-policy.md`.' "codex/README.md"; then
echo " missing: README pointer to permission profile policy"
permission_policy_ok=false
fi
if ! rg -q --fixed-strings 'docs/codex-permission-profiles-policy.md' "docs/codex-sandbox-execution-policy.md"; then
echo " missing: sandbox policy pointer to permission profile policy"
permission_policy_ok=false
fi
for required_policy_term in \
'codex update' \
'codex exec --json' \
'reasoning-token' \
'rollout tracing' \
'managed network' \
'Local help did not show `--full-auto`, `--permission-profile`, or' \
'Do not copy that pattern into new docs or new scripts'; do
if ! rg -q --fixed-strings "$required_policy_term" "docs/codex-permission-profiles-policy.md"; then
echo " missing policy term: $required_policy_term"
permission_policy_ok=false
fi
done
if rg -n --fixed-strings -- '--permission-profile' "scripts/codex" "scripts/codex-companion.sh" >/tmp/codex-unsupported-flags.$$ 2>/dev/null; then
echo " unsupported permission-profile flag appears in runtime scripts"
cat /tmp/codex-unsupported-flags.$$ | sed 's/^/ /'
permission_policy_ok=false
fi
if rg -n --fixed-strings -- '--sandbox-profile' "scripts/codex" "scripts/codex-companion.sh" >/tmp/codex-unsupported-flags.$$ 2>/dev/null; then
echo " unsupported sandbox-profile flag appears in runtime scripts"
cat /tmp/codex-unsupported-flags.$$ | sed 's/^/ /'
permission_policy_ok=false
fi
if rg -n --fixed-strings 'default to `--full-auto`' "docs/codex-permission-profiles-policy.md" "codex/README.md" >/tmp/codex-full-auto-default.$$ 2>/dev/null; then
echo " new docs still make --full-auto sound like a default"
cat /tmp/codex-full-auto-default.$$ | sed 's/^/ /'
permission_policy_ok=false
fi
if ! rg -q --fixed-strings 'codex update' "scripts/check-codex.sh"; then
echo " missing: scripts/check-codex.sh codex update guidance"
permission_policy_ok=false
fi
if ! rg -q --fixed-strings 'versioned / pinned update flow' "scripts/check-codex.sh"; then
echo " missing: scripts/check-codex.sh package-manager fallback guidance"
permission_policy_ok=false
fi
if $permission_policy_ok; then
log_pass "Codex permission profile policy is documented"
else
log_fail "Codex permission profile policy checks failed"
fi
rm -f /tmp/codex-unsupported-flags.$$ /tmp/codex-full-auto-default.$$ || true
log_test "Codex multi-environment write guard is wired and documented"
primary_guard_ok=true
if [ ! -x "scripts/codex-primary-environment-guard.sh" ]; then
echo " missing executable guard: scripts/codex-primary-environment-guard.sh"
primary_guard_ok=false
fi
if ! rg -q --fixed-strings 'codex-primary-environment-guard.sh' "scripts/codex-companion.sh" "scripts/codex/codex-exec-wrapper.sh"; then
echo " missing guard wiring in Codex write entrypoints"
primary_guard_ok=false
fi
if ! rg -q --fixed-strings 'HARNESS_CODEX_EXECUTION_ROOT="${EXECUTION_ROOT}"' "scripts/codex-companion.sh"; then
echo " missing execution-root anchored guard call in scripts/codex-companion.sh"
primary_guard_ok=false
fi
if ! rg -q --fixed-strings 'HARNESS_CODEX_ALLOW_NON_PRIMARY_WRITE=1' "codex/README.md"; then
echo " missing override guidance in codex/README.md"
primary_guard_ok=false
fi
if ! rg -q --fixed-strings 'HARNESS_CODEX_RESET_PRIMARY_ENVIRONMENT=1' "codex/README.md"; then
echo " missing primary reset guidance in codex/README.md"
primary_guard_ok=false
fi
if ! rg -q --fixed-strings 'HARNESS_CODEX_DISABLE_PRIMARY_ENV_GUARD=1' "codex/README.md"; then
echo " missing guard disable guidance in codex/README.md"
primary_guard_ok=false
fi
if $primary_guard_ok; then
log_pass "Codex multi-environment write guard is wired and documented"
else
log_fail "Codex multi-environment write guard checks failed"
fi
log_test "primary environment guard behavior"
if bash tests/test-codex-primary-environment-guard.sh >/tmp/codex-primary-env-guard.$$ 2>&1; then
log_pass "Primary environment guard behavior works"
else
cat /tmp/codex-primary-env-guard.$$ | sed 's/^/ /'
log_fail "Primary environment guard behavior failed"
fi
rm -f /tmp/codex-primary-env-guard.$$ || true
log_test "codex-setup-local cleans duplicate frontmatter skill aliases"
if PROJECT_ROOT="$PROJECT_ROOT" bash -lc '
set -euo pipefail
project_root="$PROJECT_ROOT"
tmp_home="$(mktemp -d)"
trap "rm -rf \"$tmp_home\"" EXIT
export HOME="$tmp_home"
export CODEX_HOME="$tmp_home/.codex"
mkdir -p "$CODEX_HOME/skills/legacy-harness-plan/references"
cat > "$CODEX_HOME/skills/legacy-harness-plan/SKILL.md" <<'"'"'EOF'"'"'
---
name: harness-plan
description: duplicate frontmatter name for migration test
allowed-tools: ["Read"]
---
EOF
CLAUDE_PLUGIN_ROOT="$project_root" bash "$project_root/scripts/codex-setup-local.sh" --user >/dev/null
test -f "$CODEX_HOME/skills/harness-plan/SKILL.md"
if [ -d "$CODEX_HOME/skills/legacy-harness-plan" ]; then
echo " duplicate alias directory still exists"
exit 1
fi
_alias_backup_dirs="$(find "$CODEX_HOME/backups/codex-setup-local" -type d -name "legacy-harness-plan.*" || true)"
if ! grep -q . <<<"$_alias_backup_dirs"; then
echo " duplicate alias backup not found"
exit 1
fi
'; then
log_pass "Duplicate frontmatter alias cleanup works"
else
log_fail "Duplicate frontmatter alias cleanup failed"
fi
log_test "codex-setup-local archives removed legacy Harness skills"
if PROJECT_ROOT="$PROJECT_ROOT" bash -lc '
set -euo pipefail
project_root="$PROJECT_ROOT"
tmp_home="$(mktemp -d)"
trap "rm -rf \"$tmp_home\"" EXIT
export HOME="$tmp_home"
export CODEX_HOME="$tmp_home/.codex"
mkdir -p "$CODEX_HOME/skills/work" "$CODEX_HOME/skills/plan-with-agent" "$CODEX_HOME/skills/custom-helper"
cat > "$CODEX_HOME/skills/work/SKILL.md" <<'"'"'EOF'"'"'
---
name: work
description: Claude Code Harness legacy work skill
allowed-tools: ["Read"]
---
# Harness v3 legacy work skill
Plans.md
EOF
cat > "$CODEX_HOME/skills/plan-with-agent/SKILL.md" <<'"'"'EOF'"'"'
---
name: plan-with-agent
description: Claude Code Harness legacy plan skill
allowed-tools: ["Read"]
---
# Claude Code Harness legacy planner
/harness-plan
EOF
cat > "$CODEX_HOME/skills/custom-helper/SKILL.md" <<'"'"'EOF'"'"'
---
name: custom-helper
description: personal custom skill
allowed-tools: ["Read"]
---
EOF
CLAUDE_PLUGIN_ROOT="$project_root" bash "$project_root/scripts/codex-setup-local.sh" --user >/dev/null
test -d "$CODEX_HOME/skills/harness-work"
test -d "$CODEX_HOME/skills/custom-helper"
if [ -d "$CODEX_HOME/skills/work" ] || [ -d "$CODEX_HOME/skills/plan-with-agent" ]; then
echo " legacy harness skills still exist"
exit 1
fi
_legacy_backup_dirs="$(find "$CODEX_HOME/backups/codex-setup-local" -type d \( -name "work.*" -o -name "plan-with-agent.*" \) || true)"
if ! grep -q . <<<"$_legacy_backup_dirs"; then
echo " removed legacy harness skills were not archived"
exit 1
fi
'; then
log_pass "Removed legacy Harness skills are archived safely"
else
log_fail "Removed legacy Harness skills were not archived correctly"
fi
log_test "Skills parity by SKILL name"
if [ -d "opencode/skills" ] && [ -d "codex/.codex/skills" ]; then
normalize_cross_client_skill_name() {
printf '%s\n' "$1" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-+/-/g'
}
get_skill_names() {
local root="$1"
find "$root" -mindepth 1 -maxdepth 1 -type d | while IFS= read -r d; do
local dirname
dirname="$(basename "$d")"
case "$dirname" in
test-*|x-*|breezing|cc-update-review|claude-codex-upstream-update|harness-release-internal|maintenance|zz-review-empty|zz-review-escape|_archived|harness-ui) continue ;;
esac
if [ -f "$d/SKILL.md" ]; then
local skill_name
skill_name="$(sed -n 's/^name:[[:space:]]*//p' "$d/SKILL.md" | head -n 1 | tr -d '\"')"
skill_name="$(normalize_cross_client_skill_name "$skill_name")"
printf '%s\n' "$skill_name"
fi
done | sort
}
source_list=$(get_skill_names opencode/skills)
target_list=$(get_skill_names codex/.codex/skills)
if diff -u <(echo "$source_list") <(echo "$target_list") >/dev/null; then
log_pass "Skill names match"
else
echo "[DETAIL] opencode vs codex skill names differ"
diff -u <(echo "$source_list") <(echo "$target_list") || true
log_fail "Skill names mismatch"
fi
else
log_fail "Skills directories missing"
fi
log_test "Each Codex skill has SKILL.md"
missing_skill=false
while IFS= read -r skill_dir; do
skill_name="$(basename "$skill_dir")"
case "$skill_name" in
_archived|harness-ui)
continue
;;
esac
if [ ! -f "$skill_dir/SKILL.md" ]; then
echo " missing: $skill_dir/SKILL.md"
missing_skill=true
fi
done < <(find codex/.codex/skills -mindepth 1 -maxdepth 1 -type d | sort)
if $missing_skill; then
log_fail "Missing SKILL.md"
else
log_pass "All skills have SKILL.md"
fi
if [ "$FAILED" -eq 0 ]; then
echo "All tests passed: $PASSED"
exit 0
fi
echo "Tests failed: $FAILED (passed: $PASSED)"
exit 1