From b8cb89f2c4f7006b17b4aa5d8b39b95b93b559d8 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 24 Mar 2026 10:19:38 -0500 Subject: [PATCH] codex: support store-path skill sources Handle per-skill values that come through as store-path strings, such as fetcher outputs with subdirectories appended. This restores the previously working pattern for packaged skill directories and adds a regression test for both directory and file sources. Signed-off-by: Austin Horstman --- modules/programs/codex.nix | 8 +++-- tests/modules/programs/codex/default.nix | 1 + .../programs/codex/skills-store-path.nix | 34 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tests/modules/programs/codex/skills-store-path.nix diff --git a/modules/programs/codex.nix b/modules/programs/codex.nix index c238c144..7dfe9dba 100644 --- a/modules/programs/codex.nix +++ b/modules/programs/codex.nix @@ -150,9 +150,13 @@ in # TODO: Remove this workaround once Codex supports symlinked SKILL.md # files again. Upstream only supports symlinking the containing skill # directory today: https://github.com/openai/codex/issues/10470 + isStorePathString = content: builtins.isString content && lib.hasPrefix builtins.storeDir content; + isPathLikeContent = content: lib.isPath content || isStorePathString content; mkSkillDir = content: - pkgs.writeTextDir "SKILL.md" (if lib.isPath content then builtins.readFile content else content); + pkgs.writeTextDir "SKILL.md" ( + if isPathLikeContent content then builtins.readFile content else content + ); skillSources = if builtins.isAttrs cfg.skills then cfg.skills @@ -162,7 +166,7 @@ in { }; mkSkillEntry = name: content: - if lib.isPath content && lib.pathIsDirectory content then + if isPathLikeContent content && lib.pathIsDirectory content then lib.nameValuePair "${skillsDir}/${name}" { source = content; } diff --git a/tests/modules/programs/codex/default.nix b/tests/modules/programs/codex/default.nix index 86e6e623..756b3426 100644 --- a/tests/modules/programs/codex/default.nix +++ b/tests/modules/programs/codex/default.nix @@ -9,5 +9,6 @@ codex-skills-inline-null-package = ./skills-inline-null-package.nix; codex-skills-inline-legacy-path = ./skills-inline-legacy-path.nix; codex-skills-dir = ./skills-dir.nix; + codex-skills-store-path = ./skills-store-path.nix; codex-skills-path-not-directory = ./skills-path-not-directory.nix; } diff --git a/tests/modules/programs/codex/skills-store-path.nix b/tests/modules/programs/codex/skills-store-path.nix new file mode 100644 index 00000000..3eb654ac --- /dev/null +++ b/tests/modules/programs/codex/skills-store-path.nix @@ -0,0 +1,34 @@ +{ pkgs, ... }: +let + src = pkgs.writeTextDir "skills/external-skill/SKILL.md" '' + --- + name: external-skill + description: Store path skill fixture. + --- + + # External Skill + + This content simulates a skill living inside a package source. + ''; +in +{ + programs.codex = { + enable = true; + skills = { + dir-skill = "${src}/skills/external-skill"; + file-skill = "${src}/skills/external-skill/SKILL.md"; + }; + }; + + nmt.script = '' + assertLinkExists home-files/.agents/skills/dir-skill + assertFileExists home-files/.agents/skills/dir-skill/SKILL.md + assertFileContent home-files/.agents/skills/dir-skill/SKILL.md \ + "${src}/skills/external-skill/SKILL.md" + + assertLinkExists home-files/.agents/skills/file-skill + assertFileExists home-files/.agents/skills/file-skill/SKILL.md + assertFileContent home-files/.agents/skills/file-skill/SKILL.md \ + "${src}/skills/external-skill/SKILL.md" + ''; +}