codex: add support for managed rules files

This commit is contained in:
Austin Horstman
2026-04-01 23:29:17 -05:00
parent 6267895e98
commit 41e6e2ab37
5 changed files with 111 additions and 1 deletions
@@ -0,0 +1,14 @@
{ config, ... }:
{
time = "2026-04-02T00:30:59+00:00";
condition = config.programs.codex.enable;
message = ''
The `programs.codex.rules` option was added to manage Codex `.rules`
files declaratively.
Each rule is written under `CODEX_HOME/rules/`, with attribute names
mapped to `.rules` filenames automatically. Codex uses these rules for
persistent command-prefix decisions such as allowing recurring safe
escalations without prompting every time.
'';
}
+37 -1
View File
@@ -137,6 +137,30 @@ in
}
'';
};
rules = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.lines lib.types.path);
default = { };
description = ''
Codex rules files to manage under {file}`CODEX_HOME/rules/`.
The attribute name becomes the filename, with a {file}`.rules`
extension added automatically. The value is either:
- Inline content as a string
- A path to an existing rules file
This is useful for declaratively managing persistent
`prefix_rule()` definitions, including the default
{file}`default.rules` allow-list Codex writes when you accept
recurring approvals interactively.
'';
example = lib.literalExpression ''
{
default = "prefix_rule(pattern = [\"nix\", \"build\"], decision = \"allow\")\n";
github = ./codex/github.rules;
}
'';
};
};
config =
@@ -174,6 +198,11 @@ in
lib.nameValuePair "${skillsDir}/${name}" {
source = mkSkillDir content;
};
mkRuleEntry =
name: content:
lib.nameValuePair "${configDir}/rules/${name}.rules" (
if isPathLikeContent content then { source = content; } else { text = content; }
);
transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) (
lib.mapAttrs (
@@ -207,6 +236,12 @@ in
assertion = !lib.isPath cfg.skills || lib.pathIsDirectory cfg.skills;
message = "`programs.codex.skills` must be a directory when set to a path";
}
{
assertion = lib.all (content: !(isPathLikeContent content && lib.pathIsDirectory content)) (
lib.attrValues cfg.rules
);
message = "`programs.codex.rules` attribute values must be files when set to paths";
}
];
home = {
@@ -220,7 +255,8 @@ in
text = cfg.custom-instructions;
};
}
// lib.mapAttrs' mkSkillEntry skillSources;
// lib.mapAttrs' mkSkillEntry skillSources
// lib.mapAttrs' mkRuleEntry cfg.rules;
sessionVariables = mkIf useXdgDirectories {
CODEX_HOME = "${config.xdg.configHome}/codex";
+1
View File
@@ -5,6 +5,7 @@
codex-empty-settings = ./empty-settings.nix;
codex-mcp-integration = ./mcp-integration.nix;
codex-mcp-integration-with-override = ./mcp-integration-with-override.nix;
codex-rules = ./rules.nix;
codex-skills-inline = ./skills-inline.nix;
codex-skills-inline-null-package = ./skills-inline-null-package.nix;
codex-skills-inline-legacy-path = ./skills-inline-legacy-path.nix;
+43
View File
@@ -0,0 +1,43 @@
{
programs.codex = {
enable = true;
rules = {
default = builtins.toFile "default.rules" ''
prefix_rule(
pattern = ["git", "status"],
decision = "allow",
justification = "Allow routine status checks",
)
'';
github = ''
prefix_rule(
pattern = ["gh", "pr", "view"],
decision = "prompt",
justification = "Review PRs with confirmation",
)
'';
};
};
nmt.script = ''
assertFileExists home-files/.codex/rules/default.rules
assertFileContent home-files/.codex/rules/default.rules \
${builtins.toFile "expected-default.rules" ''
prefix_rule(
pattern = ["git", "status"],
decision = "allow",
justification = "Allow routine status checks",
)
''}
assertFileExists home-files/.codex/rules/github.rules
assertFileContent home-files/.codex/rules/github.rules \
${builtins.toFile "expected-github.rules" ''
prefix_rule(
pattern = ["gh", "pr", "view"],
decision = "prompt",
justification = "Review PRs with confirmation",
)
''}
'';
}
@@ -11,6 +11,13 @@ in
programs.codex = {
enable = true;
package = codexPackage;
rules.default = ''
prefix_rule(
pattern = ["nix", "build"],
decision = "allow",
justification = "Allow local builds",
)
'';
settings = {
model = "gemma3:latest";
model_provider = "ollama";
@@ -36,5 +43,14 @@ in
assertFileExists home-files/.config/codex/AGENTS.md
assertFileContent home-files/.config/codex/AGENTS.md \
${./AGENTS.md}
assertFileExists home-files/.config/codex/rules/default.rules
assertFileContent home-files/.config/codex/rules/default.rules \
${builtins.toFile "expected-xdg-default.rules" ''
prefix_rule(
pattern = ["nix", "build"],
decision = "allow",
justification = "Allow local builds",
)
''}
'';
}