fix(config): harden doctor checks and enable Codex full access
CI / CI configuration security checks (push) Successful in 3s
CI / Capabilities and shell quality (push) Successful in 18s
CI / Python tests and compilation (push) Successful in 25s
CI / QMD unit tests (push) Successful in 11s
CI / Build and inspect generated packages (push) Successful in 17s
CI / PowerShell tests on Ubuntu (push) Successful in 1m11s
CI / Installed client configuration smoke tests (push) Successful in 1m10s
CI / Bash integration and installation guards (push) Failing after 1m13s

This commit is contained in:
Julian lechner committed 2026-10-08 15:58:47 +02:00
1 parent 8aae370bdb
commit 5e80d30936
11 files changed
+113 -24

No files matched your search

+26 -5
View File
@@ -4,6 +4,7 @@ $ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
. (Join-Path $root 'scripts/lib/manifest.ps1')
$homePath = [Environment]::GetFolderPath('UserProfile')
if ([string]::IsNullOrWhiteSpace($homePath)) { $homePath = $HOME }
$script:fail = $false
$script:checkCount = 0
function Result($state, $message) {
@@ -14,7 +15,27 @@ function Result($state, $message) {
}
}
foreach ($tool in @('codex','claude')) { if (Get-Command $tool -ErrorAction SilentlyContinue) { $version = & $tool --version 2>&1 | Select-Object -First 1; Result 'PASS' "$tool available ($version)" } else { Result 'WARN' "$tool unavailable" } }
function Invoke-DoctorCommand {
param([string]$Command, [string[]]$Arguments)
# PowerShell 5.1 turns native stderr into error records, even when the process succeeds.
$ErrorActionPreference = 'Continue'
$PSNativeCommandUseErrorActionPreference = $false
try {
$executable = Get-Command $Command -ErrorAction Stop
$output = @(& $executable @Arguments 2>&1)
return [pscustomobject]@{ Output = $output; ExitCode = $LASTEXITCODE }
} catch {
return [pscustomobject]@{ Output = @($_); ExitCode = 1 }
}
}
foreach ($tool in @('codex','claude')) {
if (Get-Command $tool -ErrorAction SilentlyContinue) {
$probe = Invoke-DoctorCommand -Command $tool -Arguments @('--version')
if ($probe.ExitCode -eq 0) { Result 'PASS' "$tool available ($($probe.Output -join ' '))" }
else { Result 'FAIL' "$tool --version failed (exit $($probe.ExitCode)): $($probe.Output -join ' ')" }
} else { Result 'WARN' "$tool unavailable" }
}
if (Get-Command jq -ErrorAction SilentlyContinue) { Result 'PASS' 'jq available for Claude Bash status line' } else { Result 'WARN' 'jq unavailable; Claude Bash status line is disabled' }
$sourceAgentCount = @(Get-ChildItem (Join-Path $root 'shared/agents') -Directory -ErrorAction SilentlyContinue).Count
@@ -50,14 +71,14 @@ if (Test-Path -LiteralPath $settingsPath) {
}
$configTomlPath = Join-Path $homePath '.codex/config.toml'
if (Test-Path -LiteralPath $configTomlPath) {
$parseResult = & python -c "import sys,tomllib,pathlib; tomllib.loads(pathlib.Path(sys.argv[1]).read_text(encoding='utf-8-sig'))" $configTomlPath 2>&1
if ($LASTEXITCODE -eq 0) { Result 'PASS' 'installed .codex/config.toml parses as TOML' } else { Result 'FAIL' "installed .codex/config.toml is invalid TOML: $parseResult" }
$parseResult = Invoke-DoctorCommand -Command 'python' -Arguments @('-c', "import sys,tomllib,pathlib; tomllib.loads(pathlib.Path(sys.argv[1]).read_text(encoding='utf-8-sig'))", $configTomlPath)
if ($parseResult.ExitCode -eq 0) { Result 'PASS' 'installed .codex/config.toml parses as TOML' } else { Result 'FAIL' "installed .codex/config.toml is invalid TOML: $($parseResult.Output -join ' ')" }
if (Get-Command codex -ErrorAction SilentlyContinue) {
$previousCodexHome = $env:CODEX_HOME
try {
$env:CODEX_HOME = Split-Path $configTomlPath -Parent
$schemaResult = & codex --strict-config --help 2>&1
if ($LASTEXITCODE -eq 0) { Result 'PASS' 'installed .codex/config.toml matches the installed Codex schema' } else { Result 'FAIL' "installed .codex/config.toml has unsupported Codex settings: $schemaResult" }
$schemaResult = Invoke-DoctorCommand -Command 'codex' -Arguments @('--strict-config', '--help')
if ($schemaResult.ExitCode -eq 0) { Result 'PASS' 'installed .codex/config.toml matches the installed Codex schema' } else { Result 'FAIL' "installed .codex/config.toml has unsupported Codex settings: $($schemaResult.Output -join ' ')" }
} finally { $env:CODEX_HOME = $previousCodexHome }
} else { Result 'WARN' 'Codex CLI unavailable; skipped installed Codex schema validation' }
}
+7 -1
View File
@@ -24,7 +24,13 @@ result() {
}
for tool in codex claude; do
if command -v "$tool" >/dev/null; then result PASS "$tool available ($($tool --version 2>&1 | head -n 1))"; else result WARN "$tool unavailable"; fi
if command -v "$tool" >/dev/null; then
if version=$("$tool" --version 2>&1); then result PASS "$tool available ($version)"
else
status=$?
result FAIL "$tool --version failed (exit $status): $version"
fi
else result WARN "$tool unavailable"; fi
done
command -v jq >/dev/null && result PASS 'jq available for Claude Bash status line' || result WARN 'jq unavailable; Claude Bash status line is disabled'
+12
View File
@@ -2,6 +2,18 @@
param([switch]$Summary)
$ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$doctorPath = Join-Path $root 'scripts/commands/doctor.ps1'
$doctorAst = [System.Management.Automation.Language.Parser]::ParseFile($doctorPath, [ref]$null, [ref]$null)
$probe = $doctorAst.Find({ param($node) $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq 'Invoke-DoctorCommand' }, $true)
if (-not $probe) { throw 'Doctor command probe is missing.' }
Invoke-Expression $probe.Extent.Text
foreach ($code in @(0, 7)) {
$result = Invoke-DoctorCommand -Command 'python' -Arguments @('-c', "import sys; print('fixture 1.0'); print('fixture warning', file=sys.stderr); sys.exit($code)")
if ($result.ExitCode -ne $code -or ($result.Output -join ' ') -notmatch 'fixture warning' -or ($result.Output -join ' ') -notmatch 'fixture 1.0') { throw 'Doctor must capture native warnings and preserve the exit code.' }
if ($ErrorActionPreference -ne 'Stop') { throw 'Doctor command probe changed the caller error preference.' }
}
$missing = Invoke-DoctorCommand -Command 'ai-config-missing-doctor-test-command'
if ($missing.ExitCode -eq 0) { throw 'Doctor must not report a missing command as successful.' }
$doctorSummaryOutput = & (Join-Path $root 'scripts/commands/doctor.ps1') -Summary
if (@($doctorSummaryOutput | Where-Object { $_ -match '^PASS ' }).Count -ne 0) { throw 'Doctor summary mode must not print individual PASS lines.' }
if (@($doctorSummaryOutput | Where-Object { $_ -match '^Doctor: PASS \| \d+ checks$' }).Count -ne 1) { throw "Doctor summary mode must print one 'Doctor: PASS | N checks' line: $($doctorSummaryOutput -join '; ')" }
+16
View File
@@ -8,6 +8,22 @@ while [ $# -gt 0 ]; do
esac
done
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
(
codex() { printf 'fixture warning\n' >&2; printf 'fixture 1.0\n'; return "$DOCTOR_TEST_CODE"; }
export -f codex
for DOCTOR_TEST_CODE in 0 7; do
export DOCTOR_TEST_CODE
if probe_output=$(bash "$root/scripts/commands/doctor.sh" --summary); then probe_status=0; else probe_status=$?; fi
if [ "$DOCTOR_TEST_CODE" -eq 0 ]; then
test "$probe_status" -eq 0
printf '%s\n' "$probe_output" | grep -Eq '^Doctor: PASS \| [0-9]+ checks$'
else
test "$probe_status" -eq 1
printf '%s\n' "$probe_output" | grep -Fq 'FAIL codex --version failed (exit 7): fixture warning'
printf '%s\n' "$probe_output" | grep -Eq '^Doctor: FAIL \| [0-9]+ checks$'
fi
done
)
doctor_summary_output=$(bash "$root/scripts/commands/doctor.sh" --summary)
if printf '%s\n' "$doctor_summary_output" | grep -q '^PASS '; then
printf 'Doctor summary mode must not print individual PASS lines.\n' >&2
+15
View File
@@ -18,6 +18,21 @@ SPEC.loader.exec_module(OPTIONS)
class InstallOptionsTests(unittest.TestCase):
def test_codex_update_replaces_old_sandbox_mode_and_preserves_user_settings(self):
current = ('sandbox_mode = "workspace-write"\nmodel = "custom-model"\n'
'[windows]\nsandbox = "elevated"\n'
'[sandbox_workspace_write]\nnetwork_access = false\n'
'[mcp_servers.demo]\ncommand = "demo"\n')
managed = (ROOT / 'adapters/codex/config/config.toml').read_text(encoding='utf-8')
result = tomllib.loads(OPTIONS.merge_toml(current, managed))
self.assertEqual(result['sandbox_mode'], 'danger-full-access')
self.assertEqual(result['approval_policy'], 'on-request')
self.assertEqual(result['approvals_reviewer'], 'auto_review')
self.assertEqual(result['model'], 'custom-model')
self.assertEqual(result['mcp_servers']['demo'], {'command': 'demo'})
def test_merge_toml_preserves_local_plugin_table_indentation(self):
plugin_state = (' [marketplaces.ponytail]\n'
' source_type = "git"\n'
+1
View File
@@ -63,6 +63,7 @@ foreach ($shell in @('powershell','bash')) {
$events = @([regex]::Matches($content, '(?m)^\[\[hooks\.([^.\]]+)\]\]\s*$') | ForEach-Object { $_.Groups[1].Value })
if ((($events | Sort-Object) -join ',') -ne 'SessionStart,Stop' -or $content -match 'flashbang-if-input' -or $content -match '(?m)^async\s*=\s*true\s*$') { throw "Codex finish hook is incorrect in $package" }
if ($content -notmatch 'approvals_reviewer\s*=\s*"auto_review"') { throw "Codex auto review is missing in $package" }
if ($content -notmatch '(?m)^sandbox_mode\s*=\s*"danger-full-access"\s*$') { throw "Codex full access is missing in $package" }
if ($content -notmatch '(?m)^max_depth\s*=\s*1\s*$') { throw "Codex agent depth limit is missing in $package" }
if ($content -notmatch 'status_line\s*=\s*\["model", "reasoning", "fast-mode", "approval-mode", "five-hour-limit", "weekly-limit", "project-name", "git-branch", "context-window-size", "context-used", "used-tokens"\]') { throw "Codex status line is incorrect in $package" }
} elseif ((($settings.hooks.PSObject.Properties.Name | Sort-Object) -join ',') -ne 'PreCompact,SessionStart,Stop' -or $content -match 'flashbang-if-input' -or $settings.hooks.Stop[0].hooks[0].async) {
+1
View File
@@ -83,6 +83,7 @@ for shell in powershell bash; do
! grep -q 'flashbang-if-input' "$package/$file"
! grep -Eq '^async[[:space:]]*=[[:space:]]*true[[:space:]]*$' "$package/$file"
grep -q 'approvals_reviewer[[:space:]]*=[[:space:]]*"auto_review"' "$package/$file"
grep -Eq '^sandbox_mode[[:space:]]*=[[:space:]]*"danger-full-access"[[:space:]]*$' "$package/$file"
grep -Eq '^max_depth[[:space:]]*=[[:space:]]*1[[:space:]]*$' "$package/$file"
grep -Fq 'status_line = ["model", "reasoning", "fast-mode", "approval-mode", "five-hour-limit", "weekly-limit", "project-name", "git-branch", "context-window-size", "context-used", "used-tokens"]' "$package/$file"
else