codex: add profile config support
Codex 0.134.0 stopped reading profile settings from [profiles.<name>] in config.toml and no longer supports the top-level profile selector. Add a dedicated programs.codex.profiles option so Home Manager can write CODEX_HOME/<name>.config.toml files that match the current CLI --profile behavior. For Codex 0.134.0 and later, legacy programs.codex.settings.profiles entries are migrated into profile files with a warning, and removed legacy profile keys are omitted from config.toml so strict config loading keeps working. Older Codex versions keep the legacy config shape.
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
time = "2026-06-08T12:00:00+00:00";
|
||||||
|
condition = config.programs.codex.enable;
|
||||||
|
message = ''
|
||||||
|
The `programs.codex.profiles` option was added to manage Codex CLI
|
||||||
|
profile files under `CODEX_HOME`.
|
||||||
|
|
||||||
|
These files are selected with `codex --profile <name>`, matching the
|
||||||
|
profile behavior used by Codex 0.134.0 and later.
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -13,8 +13,11 @@ let
|
|||||||
yamlFormat = pkgs.formats.yaml { };
|
yamlFormat = pkgs.formats.yaml { };
|
||||||
jsonFormat = pkgs.formats.json { };
|
jsonFormat = pkgs.formats.json { };
|
||||||
|
|
||||||
packageVersion = if cfg.package != null then lib.getVersion cfg.package else "0.94.0";
|
# A null package has no detectable version, so assume the latest Codex and
|
||||||
isTomlConfig = lib.versionAtLeast packageVersion "0.2.0";
|
# enable version-gated behavior by default.
|
||||||
|
atLeast = version: cfg.package == null || lib.versionAtLeast (lib.getVersion cfg.package) version;
|
||||||
|
isTomlConfig = atLeast "0.2.0";
|
||||||
|
migrateLegacyProfiles = atLeast "0.134.0";
|
||||||
settingsFormat = if isTomlConfig then tomlFormat else yamlFormat;
|
settingsFormat = if isTomlConfig then tomlFormat else yamlFormat;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -82,6 +85,32 @@ in
|
|||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
profiles = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf tomlFormat.type;
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Named Codex configuration profiles written to
|
||||||
|
{file}`CODEX_HOME/<name>.config.toml`.
|
||||||
|
|
||||||
|
These profiles are selected with {command}`codex --profile <name>`.
|
||||||
|
Codex 0.134.0 and later no longer reads profile settings from
|
||||||
|
{option}`programs.codex.settings.profiles`, and the top-level
|
||||||
|
{option}`programs.codex.settings.profile` selector is no longer
|
||||||
|
supported.
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
deep-review = {
|
||||||
|
model = "gpt-5.5";
|
||||||
|
model_reasoning_effort = "xhigh";
|
||||||
|
approval_policy = "on-request";
|
||||||
|
sandbox_mode = "workspace-write";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
context = lib.mkOption {
|
context = lib.mkOption {
|
||||||
type = lib.types.either lib.types.lines lib.types.path;
|
type = lib.types.either lib.types.lines lib.types.path;
|
||||||
description = ''
|
description = ''
|
||||||
@@ -336,6 +365,12 @@ in
|
|||||||
};
|
};
|
||||||
category = "Productivity";
|
category = "Productivity";
|
||||||
};
|
};
|
||||||
|
mkProfileEntry =
|
||||||
|
name: settings:
|
||||||
|
lib.nameValuePair "${configDir}/${name}.config.toml" {
|
||||||
|
source = tomlFormat.generate "codex-${name}-config" settings;
|
||||||
|
};
|
||||||
|
|
||||||
transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) (
|
transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) (
|
||||||
lib.mapAttrs (
|
lib.mapAttrs (
|
||||||
name: server:
|
name: server:
|
||||||
@@ -358,6 +393,23 @@ in
|
|||||||
) config.programs.mcp.servers
|
) config.programs.mcp.servers
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# TODO: remove this migration block in a future stateVersion once the
|
||||||
|
# Codex 0.134 profile transition window has passed.
|
||||||
|
hasLegacyProfileSettings =
|
||||||
|
migrateLegacyProfiles && ((rawSettings ? profile) || (rawSettings ? profiles));
|
||||||
|
legacyProfiles = lib.optionalAttrs (
|
||||||
|
hasLegacyProfileSettings && builtins.isAttrs (rawSettings.profiles or null)
|
||||||
|
) rawSettings.profiles;
|
||||||
|
mergedProfiles = legacyProfiles // cfg.profiles;
|
||||||
|
baseSettings =
|
||||||
|
if hasLegacyProfileSettings then
|
||||||
|
lib.removeAttrs rawSettings [
|
||||||
|
"profile"
|
||||||
|
"profiles"
|
||||||
|
]
|
||||||
|
else
|
||||||
|
rawSettings;
|
||||||
|
|
||||||
generatedPluginSettings =
|
generatedPluginSettings =
|
||||||
lib.optionalAttrs (cfg.plugins != [ ] || cfg.marketplaces != { }) {
|
lib.optionalAttrs (cfg.plugins != [ ] || cfg.marketplaces != { }) {
|
||||||
features.plugins = true;
|
features.plugins = true;
|
||||||
@@ -368,14 +420,23 @@ in
|
|||||||
// lib.optionalAttrs (cfg.marketplaces != { }) {
|
// lib.optionalAttrs (cfg.marketplaces != { }) {
|
||||||
marketplaces = lib.mapAttrs mkMarketplaceConfigEntry cfg.marketplaces;
|
marketplaces = lib.mapAttrs mkMarketplaceConfigEntry cfg.marketplaces;
|
||||||
};
|
};
|
||||||
mergedSettingsWithoutMcp = lib.recursiveUpdate rawSettings generatedPluginSettings;
|
mergedSettingsWithoutMcp = lib.recursiveUpdate baseSettings generatedPluginSettings;
|
||||||
settingMcpServers = lib.attrByPath [ "mcp_servers" ] { } mergedSettingsWithoutMcp;
|
settingMcpServers = lib.attrByPath [ "mcp_servers" ] { } mergedSettingsWithoutMcp;
|
||||||
mergedMcpServers = transformedMcpServers // settingMcpServers;
|
mergedMcpServers = transformedMcpServers // settingMcpServers;
|
||||||
|
|
||||||
mergedSettings =
|
mergedSettings =
|
||||||
mergedSettingsWithoutMcp
|
mergedSettingsWithoutMcp
|
||||||
// lib.optionalAttrs (mergedMcpServers != { }) { mcp_servers = mergedMcpServers; };
|
// lib.optionalAttrs (mergedMcpServers != { }) { mcp_servers = mergedMcpServers; };
|
||||||
in
|
in
|
||||||
mkIf cfg.enable {
|
mkIf cfg.enable {
|
||||||
|
warnings = lib.optional hasLegacyProfileSettings ''
|
||||||
|
`programs.codex.settings.profile` and `programs.codex.settings.profiles`
|
||||||
|
are no longer supported by Codex 0.134.0 and later. Home Manager
|
||||||
|
now writes entries from `programs.codex.settings.profiles` to
|
||||||
|
`CODEX_HOME/<name>.config.toml`. Move them to
|
||||||
|
`programs.codex.profiles` and remove `programs.codex.settings.profile`.
|
||||||
|
'';
|
||||||
|
|
||||||
assertions = [
|
assertions = [
|
||||||
{
|
{
|
||||||
assertion = (cfg.plugins == [ ] && cfg.marketplaces == { }) || isTomlConfig;
|
assertion = (cfg.plugins == [ ] && cfg.marketplaces == { }) || isTomlConfig;
|
||||||
@@ -448,6 +509,7 @@ in
|
|||||||
text = cfg.context;
|
text = cfg.context;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// lib.mapAttrs' mkProfileEntry mergedProfiles
|
||||||
// lib.mapAttrs' mkSkillEntry skillSources
|
// lib.mapAttrs' mkSkillEntry skillSources
|
||||||
// lib.listToAttrs (map mkPluginFileEntry cfg.plugins)
|
// lib.listToAttrs (map mkPluginFileEntry cfg.plugins)
|
||||||
// lib.mapAttrs' mkRuleEntry cfg.rules;
|
// lib.mapAttrs' mkRuleEntry cfg.rules;
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
codex-mcp-integration = ./mcp-integration.nix;
|
codex-mcp-integration = ./mcp-integration.nix;
|
||||||
codex-mcp-integration-with-override = ./mcp-integration-with-override.nix;
|
codex-mcp-integration-with-override = ./mcp-integration-with-override.nix;
|
||||||
codex-plugins = ./plugins.nix;
|
codex-plugins = ./plugins.nix;
|
||||||
|
codex-profiles-legacy-old-version = ./profiles-legacy-old-version.nix;
|
||||||
|
codex-profiles-legacy-warning = ./profiles-legacy-warning.nix;
|
||||||
|
codex-profiles = ./profiles.nix;
|
||||||
codex-rules = ./rules.nix;
|
codex-rules = ./rules.nix;
|
||||||
codex-skills-inline = ./skills-inline.nix;
|
codex-skills-inline = ./skills-inline.nix;
|
||||||
codex-skills-inline-null-package = ./skills-inline-null-package.nix;
|
codex-skills-inline-null-package = ./skills-inline-null-package.nix;
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
approval_policy = "never"
|
||||||
|
sandbox_mode = "workspace-write"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
model = "gpt-5"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
approval_policy = "on-request"
|
||||||
|
model = "gpt-5.5"
|
||||||
|
model_reasoning_effort = "xhigh"
|
||||||
|
sandbox_mode = "workspace-write"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
codexPackage = pkgs.runCommand "codex-0.133.0" { } ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
echo '#!/bin/sh' > $out/bin/codex
|
||||||
|
chmod +x $out/bin/codex
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
programs.codex = {
|
||||||
|
enable = true;
|
||||||
|
package = codexPackage;
|
||||||
|
settings = {
|
||||||
|
model = "gpt-5";
|
||||||
|
profile = "deep-review";
|
||||||
|
profiles.deep-review = {
|
||||||
|
approval_policy = "on-request";
|
||||||
|
sandbox_mode = "workspace-write";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test.asserts.warnings.expected = [ ];
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
assertFileExists home-files/.codex/config.toml
|
||||||
|
assertFileContent home-files/.codex/config.toml \
|
||||||
|
${./profiles-legacy-old-version.toml}
|
||||||
|
assertPathNotExists home-files/.codex/deep-review.config.toml
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
model = "gpt-5"
|
||||||
|
profile = "deep-review"
|
||||||
|
|
||||||
|
[profiles.deep-review]
|
||||||
|
approval_policy = "on-request"
|
||||||
|
sandbox_mode = "workspace-write"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
approval_policy = "on-request"
|
||||||
|
sandbox_mode = "workspace-write"
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
codexPackage = pkgs.runCommand "codex-0.134.0" { } ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
echo '#!/bin/sh' > $out/bin/codex
|
||||||
|
chmod +x $out/bin/codex
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
programs.codex = {
|
||||||
|
enable = true;
|
||||||
|
package = codexPackage;
|
||||||
|
settings = {
|
||||||
|
model = "gpt-5";
|
||||||
|
profile = "deep-review";
|
||||||
|
profiles.deep-review = {
|
||||||
|
approval_policy = "on-request";
|
||||||
|
sandbox_mode = "workspace-write";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test.asserts.warnings.expected = [
|
||||||
|
''
|
||||||
|
`programs.codex.settings.profile` and `programs.codex.settings.profiles`
|
||||||
|
are no longer supported by Codex 0.134.0 and later. Home Manager
|
||||||
|
now writes entries from `programs.codex.settings.profiles` to
|
||||||
|
`CODEX_HOME/<name>.config.toml`. Move them to
|
||||||
|
`programs.codex.profiles` and remove `programs.codex.settings.profile`.
|
||||||
|
''
|
||||||
|
];
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
assertFileExists home-files/.codex/config.toml
|
||||||
|
assertFileContent home-files/.codex/config.toml \
|
||||||
|
${./profiles-legacy-warning.toml}
|
||||||
|
assertFileExists home-files/.codex/deep-review.config.toml
|
||||||
|
assertFileContent home-files/.codex/deep-review.config.toml \
|
||||||
|
${./profiles-legacy-warning-deep-review.toml}
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
model = "gpt-5"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
codexPackage = pkgs.runCommand "codex-0.134.0" { } ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
echo '#!/bin/sh' > $out/bin/codex
|
||||||
|
chmod +x $out/bin/codex
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
programs.codex = {
|
||||||
|
enable = true;
|
||||||
|
package = codexPackage;
|
||||||
|
settings = {
|
||||||
|
model = "gpt-5";
|
||||||
|
};
|
||||||
|
profiles = {
|
||||||
|
deep-review = {
|
||||||
|
approval_policy = "on-request";
|
||||||
|
model = "gpt-5.5";
|
||||||
|
model_reasoning_effort = "xhigh";
|
||||||
|
sandbox_mode = "workspace-write";
|
||||||
|
};
|
||||||
|
ci = {
|
||||||
|
approval_policy = "never";
|
||||||
|
sandbox_mode = "workspace-write";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test.asserts.warnings.expected = [ ];
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
assertFileExists home-files/.codex/config.toml
|
||||||
|
assertFileContent home-files/.codex/config.toml \
|
||||||
|
${./profiles-config.toml}
|
||||||
|
assertFileExists home-files/.codex/deep-review.config.toml
|
||||||
|
assertFileContent home-files/.codex/deep-review.config.toml \
|
||||||
|
${./profiles-deep-review.toml}
|
||||||
|
assertFileExists home-files/.codex/ci.config.toml
|
||||||
|
assertFileContent home-files/.codex/ci.config.toml \
|
||||||
|
${./profiles-ci.toml}
|
||||||
|
'';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user