fix(install): link skills where codex looks

Codex 0.148 discovers skills under $CODEX_HOME/skills only, so every
skill in this repo was invisible to codex sessions — review sessions run
by a codex reviewer reported review-pr as an unavailable skill and fell
back to improvising a review.

Both installers now link each skill into ~/.codex/skills as well. The
nix module links one symlink per skill dir instead of recursive=true:
codex skips any skill whose SKILL.md is itself a symlink, which is what
a per-file link produces.
This commit is contained in:
Miguel Palhas
2026-08-22 19:50:01 +01:00
parent 0f131f00a4
commit 0096addb23
3 changed files with 27 additions and 7 deletions
+2 -2
View File
@@ -17,7 +17,7 @@ nix/home.nix # home-manager module for NixOS machines
flake.nix # exposes homeModules.default flake.nix # exposes homeModules.default
``` ```
Skills are portable: only `name`+`description` frontmatter is required by any of the tools; Claude-only fields (`user-invocable`, `args`) are ignored elsewhere. Claude Code reads them from `~/.claude/skills`, Codex and Pi from `~/.agents/skills`, and opencode auto-loads both — so the two links cover all four. Cross-skill refs use root-relative paths (`tracker-common/COMMON.md`), so they resolve under either root. Skills are portable: only `name`+`description` frontmatter is required by any of the tools; Claude-only fields (`user-invocable`, `args`) are ignored elsewhere. Claude Code reads them from `~/.claude/skills`, Pi from `~/.agents/skills`, Codex from `~/.codex/skills` (and only there — `~/.agents/skills` is invisible to it), and opencode auto-loads the first two. Cross-skill refs use root-relative paths (`tracker-common/COMMON.md`), so they resolve under either root.
Context files differ: Claude Code and Codex support `@file` imports, so their entry files import the shared fragments by path. Pi and opencode do not, so each gets a single `AGENTS.md` generated by concatenating the same fragments — on NixOS the home-manager module builds it in the store, elsewhere `bin/link.sh` writes it (idempotent; set `MACHINE=name` to pick a `claude-md/machines/` profile, default is `default`). Context files differ: Claude Code and Codex support `@file` imports, so their entry files import the shared fragments by path. Pi and opencode do not, so each gets a single `AGENTS.md` generated by concatenating the same fragments — on NixOS the home-manager module builds it in the store, elsewhere `bin/link.sh` writes it (idempotent; set `MACHINE=name` to pick a `claude-md/machines/` profile, default is `default`).
@@ -30,7 +30,7 @@ git clone https://git.naps.pt/yolo/agent-skills.git ~/tea/agent-skills
~/tea/agent-skills/bin/link.sh ~/tea/agent-skills/bin/link.sh
``` ```
Symlinks each skill into `~/.claude/skills/` and `~/.agents/skills/`, commands into `~/.claude/commands/` and `~/.config/opencode/commands/`, hooks into `~/.claude/hooks/`, `claude-md/` fragments into `~/.claude/`, and generates `~/.pi/agent/AGENTS.md` and `~/.config/opencode/AGENTS.md` from the fragments. Idempotent; any pre-existing real dir (or non-generated AGENTS.md) is moved to `~/.agent-skills-backup/` (outside the discovery path, so it isn't picked up as a duplicate skill). Re-run after adding a skill. Symlinks each skill into `~/.claude/skills/`, `~/.agents/skills/` and `~/.codex/skills/`, commands into `~/.claude/commands/` and `~/.config/opencode/commands/`, hooks into `~/.claude/hooks/`, `claude-md/` fragments into `~/.claude/`, and generates `~/.pi/agent/AGENTS.md` and `~/.config/opencode/AGENTS.md` from the fragments. Idempotent; any pre-existing real dir (or non-generated AGENTS.md) is moved to `~/.agent-skills-backup/` (outside the discovery path, so it isn't picked up as a duplicate skill). Re-run after adding a skill.
Hooks still need one manual step: the `settings.json` snippet in `hooks/README.md`. Entry files are linked automatically — `entry/CLAUDE.md` and `entry/codex-AGENTS.md` hold the machine-local sections and `@import` the shared fragments, so both tools read the same rules with no copy and no drift. Hooks still need one manual step: the `settings.json` snippet in `hooks/README.md`. Entry files are linked automatically — `entry/CLAUDE.md` and `entry/codex-AGENTS.md` hold the machine-local sections and `@import` the shared fragments, so both tools read the same rules with no copy and no drift.
+8 -4
View File
@@ -7,10 +7,13 @@ set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)" REPO="$(cd "$(dirname "$0")/.." && pwd)"
# targets: agent config skill roots. Claude Code reads ~/.claude/skills, # targets: agent config skill roots. Claude Code reads ~/.claude/skills,
# Codex and Pi read ~/.agents/skills, opencode auto-loads both dirs. # Pi reads ~/.agents/skills, opencode auto-loads both dirs, and Codex reads
# All four consume the same SKILL.md dirs. # $CODEX_HOME/skills and nothing else -- ~/.agents/skills is invisible to it,
# which is how review sessions ended up reporting an unavailable review-pr
# skill. All four consume the same SKILL.md dirs.
CLAUDE_SKILLS="$HOME/.claude/skills" CLAUDE_SKILLS="$HOME/.claude/skills"
CODEX_SKILLS="$HOME/.agents/skills" AGENTS_SKILLS="$HOME/.agents/skills"
CODEX_SKILLS="$HOME/.codex/skills"
CLAUDE_CMDS="$HOME/.claude/commands" # commands are Claude-only; Codex ignores CLAUDE_CMDS="$HOME/.claude/commands" # commands are Claude-only; Codex ignores
OPENCODE_CMDS="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/commands" OPENCODE_CMDS="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/commands"
CLAUDE_HOOKS="$HOME/.claude/hooks" # hooks are Claude-only CLAUDE_HOOKS="$HOME/.claude/hooks" # hooks are Claude-only
@@ -62,11 +65,12 @@ gen() { # gen <dst> <fragment...> — writes a generated (concatenated) file
GEN_MARK="<!-- generated by agent-skills/bin/link.sh — edit fragments, re-run -->" GEN_MARK="<!-- generated by agent-skills/bin/link.sh — edit fragments, re-run -->"
mkdir -p "$CLAUDE_SKILLS" "$CODEX_SKILLS" "$CLAUDE_CMDS" "$CLAUDE_HOOKS" "$CLAUDE_SCRIPTS" "$CLAUDE_RULES" "$CODEX_HOME" "$PI_HOME" "$OPENCODE_CMDS" mkdir -p "$CLAUDE_SKILLS" "$AGENTS_SKILLS" "$CODEX_SKILLS" "$CLAUDE_CMDS" "$CLAUDE_HOOKS" "$CLAUDE_SCRIPTS" "$CLAUDE_RULES" "$CODEX_HOME" "$PI_HOME" "$OPENCODE_CMDS"
for d in "$REPO"/skills/*/; do for d in "$REPO"/skills/*/; do
name="$(basename "$d")" name="$(basename "$d")"
link "$d" "$CLAUDE_SKILLS/$name" link "$d" "$CLAUDE_SKILLS/$name"
link "$d" "$AGENTS_SKILLS/$name"
link "$d" "$CODEX_SKILLS/$name" link "$d" "$CODEX_SKILLS/$name"
done done
+17 -1
View File
@@ -40,6 +40,22 @@ let
repo = cfg.repoPath; repo = cfg.repoPath;
mkEnable = what: lib.mkEnableOption "the ${what} user unit"; mkEnable = what: lib.mkEnableOption "the ${what} user unit";
# Codex reads $CODEX_HOME/skills and nothing else -- ~/.agents/skills reaches
# pi and opencode but not codex, which is why codex review sessions reported
# review-pr as an unavailable skill. One symlink per skill dir rather than
# recursive=true: codex skips any skill whose SKILL.md is itself a symlink,
# which is exactly what a per-file link produces. Per-skill also leaves
# codex's own ~/.codex/skills/.system in place, which a whole-dir link would
# replace.
skillNames = builtins.attrNames (
lib.filterAttrs (_: type: type == "directory") (builtins.readDir "${agent-skills}/skills")
);
codexSkills = lib.listToAttrs (
map (name: lib.nameValuePair ".codex/skills/${name}" {
source = "${agent-skills}/skills/${name}";
}) skillNames
);
in in
{ {
options.programs.agentSkills = { options.programs.agentSkills = {
@@ -71,7 +87,7 @@ in
weekReview.enable = mkEnable "weekly review timer"; weekReview.enable = mkEnable "weekly review timer";
}; };
config.home.file = { config.home.file = codexSkills // {
".claude/skills" = { ".claude/skills" = {
source = "${agent-skills}/skills"; source = "${agent-skills}/skills";
recursive = true; recursive = true;