Files
codex-ops-kit/scripts/install_codex_kit.sh
T
tevansandClaude Opus 4.8 aedbf8a515 Stop emitting legacy Codex profile config; preserve settings after managed block
Codex 0.144+ refuses to load any config containing the top-level
`profile = "..."` key or `[profiles.<name>]` tables, so every machine running
the kit's output hit "Error loading configuration" and codex would not start.

Profiles now live in per-profile files:
- templates/global/{economy,balanced,deep}.config.toml, installed to
  ~/.codex/<name>.config.toml and selected with `codex --profile <name>`
- config.toml keeps the balanced values as its defaults, so plain `codex`
  behaves as before without a profile selector

`profile` and `profiles.*` stay in the installer's strip lists so upgrading an
existing machine removes the legacy keys from its live config.

Also fixes two latent bugs in the merge, both hit while testing the above:
- Only text *before* the managed block was retained, so anything after it was
  silently deleted on every install. `codex mcp add` appends [mcp_servers.*] to
  the end of config.toml, so those servers would be wiped. Both sides are now
  kept and filtered.
- The config was read with the locale default encoding and a leading BOM was
  preserved, which could strand the BOM mid-file once content was merged around
  the block -- invalid TOML. Read as utf-8-sig, write utf-8.

Verified against a seeded legacy config (BOM at byte 0, machine-specific
[mcp_servers] on both sides of the block): legacy keys stripped, both sides
preserved, idempotent across three runs, and `codex --profile economy exec`
returns a live model response.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:10:03 +01:00

122 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
STAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP_DIR="$CODEX_HOME/backups/$STAMP"
MANAGED_CONFIG="$REPO_ROOT/templates/global/config-managed.toml"
mkdir -p "$CODEX_HOME" "$BACKUP_DIR" "$CODEX_HOME/memories"
if [[ -f "$CODEX_HOME/config.toml" ]]; then
cp "$CODEX_HOME/config.toml" "$BACKUP_DIR/config.toml.bak"
fi
if [[ -f "$CODEX_HOME/AGENTS.md" ]]; then
cp "$CODEX_HOME/AGENTS.md" "$BACKUP_DIR/AGENTS.md.bak"
fi
cp "$REPO_ROOT/templates/global/AGENTS.md" "$CODEX_HOME/AGENTS.md"
python3 - "$CODEX_HOME/config.toml" "$MANAGED_CONFIG" <<'PY'
from pathlib import Path
import sys
config_path = Path(sys.argv[1])
managed_path = Path(sys.argv[2])
managed_start = "# >>> codex-ops-kit managed block >>>"
managed_end = "# <<< codex-ops-kit managed block <<<"
# "profile" and the "profiles.*" tables are legacy: Codex 0.144+ refuses to load
# a config containing them. They stay in these strip lists (despite no longer
# being emitted) so an upgrade from an older kit removes them from a live config.
managed_root_keys = {
"model",
"model_reasoning_effort",
"profile",
"project_doc_max_bytes",
"web_search",
}
managed_sections = {
"history",
"profiles.economy",
"profiles.balanced",
"profiles.deep",
}
# utf-8-sig drops a leading BOM if one is present. Preserving it risks stranding
# the BOM mid-file once content is merged around the block, which is invalid TOML.
text = config_path.read_text(encoding="utf-8-sig") if config_path.exists() else ""
managed = managed_path.read_text(encoding="utf-8-sig").rstrip()
def strip_managed(chunk):
"""Drop kit-owned keys/tables so only machine-specific settings remain."""
filtered_lines = []
current_section = None
for line in chunk.splitlines():
stripped = line.strip()
if stripped.startswith("[") and stripped.endswith("]"):
current_section = stripped[1:-1].strip()
if current_section in managed_sections:
continue
filtered_lines.append(line)
continue
if current_section in managed_sections:
continue
if "=" in line and not line.lstrip().startswith("#"):
key = line.split("=", 1)[0].strip()
if current_section is None and key in managed_root_keys:
continue
filtered_lines.append(line)
return "\n".join(filtered_lines).strip()
# Settings may sit on either side of the managed block -- notably `codex mcp add`
# appends [mcp_servers.*] to the end of the file. Keep both sides; rewriting only
# what precedes the block silently discards everything after it.
if managed_start in text and managed_end in text:
before = text.split(managed_start, 1)[0]
after = text.split(managed_end, 1)[1]
else:
before = text
after = ""
before = strip_managed(before)
after = strip_managed(after)
block = f"{managed_start}\n{managed}\n{managed_end}\n"
parts = [part for part in (before, block.rstrip(), after) if part]
config_path.write_text("\n\n".join(parts) + "\n", encoding="utf-8")
PY
# Profile files. Codex 0.144+ reads per-profile settings from <name>.config.toml
# next to config.toml rather than from [profiles.<name>] tables. These files are
# wholly kit-managed, so they are replaced outright (after backup).
for profile in economy balanced deep; do
profile_src="$REPO_ROOT/templates/global/$profile.config.toml"
profile_dest="$CODEX_HOME/$profile.config.toml"
if [[ -f "$profile_dest" ]]; then
cp "$profile_dest" "$BACKUP_DIR/$profile.config.toml.bak"
fi
cp "$profile_src" "$profile_dest"
done
mkdir -p "$CODEX_HOME/memories/portable-kit"
cp "$REPO_ROOT"/memory/*.md "$CODEX_HOME/memories/portable-kit/"
echo "Installed Codex kit into: $CODEX_HOME"
echo "Backup created at: $BACKUP_DIR"
echo
echo "Next steps:"
echo "- Defaults match the balanced profile; plain 'codex' needs no flag."
echo "- Use 'codex --profile economy' for lighter work and '--profile deep' for hard tasks."
echo "- Run bash scripts/audit_codex_home.sh periodically."