fix(skills): route [[skill]] + plugin skills natively for Codex/Gemini (ADR-012)#67
Merged
Merged
Conversation
…i (ADR-012) PR #65 fixed only the SNAPSHOT path (agents_skills/, gemini_skills/). The two other ways a skill reaches an agent — [[skill]] by-reference in variant.toml and a [nasde.plugin]'s own skills/ — still went through stage_skill_dir's hardcoded /app/.claude/skills/ target. Correct for Claude (cwd discovery root), but for a Codex/Gemini variant the skill landed where the CLI never scans, so it was never natively registered — the same silent "skill-as-document" failure, just via a different path. Uncaught today (no non-Claude variant used [[skill]]/plugin) but a latent trap for the core skill-migration use case. Both paths now resolve to skill DIRECTORIES (plugin_registration.collect_ referenced_skill_dirs / collect_plugin_skill_dirs) and, for Codex/Gemini, route through Harbor's native config.agent.skills — unioned with the snapshot dirs inside _refresh_agent_skills under one _nasde_derived_skills marker (stale-drop and hand-authored preservation work across both sources). Claude keeps its unchanged sandbox_files path; a Claude agent drops extra_skill_dirs at merge to avoid double-injecting. The frontmatter warning now fires on this path too, and a new warning flags two derived dirs sharing a basename (Harbor resolve_skills is last-wins, so the loser would silently vanish). New example variants codex-nasde-dev-with-arch and gemini-nasde-dev-with-arch exercise [[skill]] by-reference on a non-Claude agent (and add the long-missing Codex variant to nasde-dev-skill). Config verified: the codex variant's generated harbor_config carries skills=[nasde-dev] (native) with the [[skill]] dir, not just the Claude sandbox entry. Full suite 393 passing; ruff/format/mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The frontmatter and basename-collision warning assertions matched a literal substring against capsys output, but Rich wraps the long tmp-path line on Windows' narrow CI console, splitting 'missing YAML frontmatter' across a newline. Normalize with ' '.join(out.split()) like the existing collision tests. Verified with COLUMNS=80 locally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(review) Code-review finding: `_collect_native_skill_dirs` returns `str(dir.resolve())` (resolved), but the new native channel did NOT resolve — `collect_plugin_skill_ dirs` returned `staged.staged_dir / name` and `_resolve_skill_source`'s ref branch returned `worktree / relative`, both unresolved. In `_refresh_agent_ skills` the union/dedup, `_nasde_derived_skills` stale-drop, handwritten preservation and basename-collision warning all key on string equality, so the same logical dir in two string forms (a symlinked /tmp on macOS, or a ref worktree) would fail to dedup, break stale-drop across runs, and fire a spurious collision warning. Both collect_* now `.resolve()` their dirs to match the snapshot path. Adds canonical-path regression tests. Also corrected a misleading comment claiming create_ref_worktree caches/dedups ref worktrees — it does not; the lifecycle is safe because all worktrees live in _active_worktrees until cleanup_worktrees() in the finally after the job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the hand-rolled _dedup_preserving_order helper with the stdlib idiom list(dict.fromkeys(...)) — same behavior, one line, more discoverable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The [[skill]] path is verified e2e on a live agent, but there is no example with a [nasde.plugin], so the plugin path had only unit coverage on a synthetic StagedPlugin. Add two integration tests that drive the REAL ensure_task_plugin staging through _prepare_task_environments → collect_plugin_skill_dirs → harbor_config: a plugin's own skills/ lands in native config.agent.skills (resolved) for a Codex variant, and in /app/.claude/skills sandbox_files for a Claude variant. Closes the 'does collect_plugin_skill_dirs read the right staged location' gap without Docker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t coverage) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
assert s.endswith('/plug-skill') hardcoded a forward slash and failed on
Windows where the resolved host path uses backslashes. Use Path(s).name ==
'plug-skill' instead. (The /app/.claude/... sandbox assertion is a container
posix path and stays as-is.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #65, closing the known follow-up that ADR-012 itself flagged.
Problem
A skill reaches an agent three ways: the variant snapshot (
agents_skills/,gemini_skills/),[[skill]]by-reference invariant.toml, and a[nasde.plugin]'s ownskills/. PR #65 fixed only the snapshot path. The other two still went throughplugin_registration.stage_skill_dir, which hardcodes/app/.claude/skills/<name>/:$HOME/.agents/skills,~/.gemini/skills), so it was never natively registered — the same silent "skill-as-document" failure as the original bug.Uncaught today (no non-Claude variant in the repo uses
[[skill]]/plugin), but a latent trap for exactly the core use case: migrating a by-reference skill (the ADR-009 way to avoid copy drift) between agents.Fix
Both paths now resolve to skill directories (
collect_referenced_skill_dirs/collect_plugin_skill_dirs— the dir-list counterparts of the unchanged Claude-onlystage_referenced_skills/register_plugin_skills). For Codex/Gemini those dirs route through Harbor's nativeconfig.agent.skills, unioned with the snapshot dirs inside_refresh_agent_skillsunder the single_nasde_derived_skillsmarker (so stale-drop + hand-authored preservation work across both sources).sandbox_filespath; a Claude agent dropsextra_skill_dirsat merge time to avoid double-injecting.resolve_skillsis last-wins bydir.name, so two derived dirs sharing a basename would silently collapse — nasde now warns.Branch is one place:
_refresh_agent_skillskeys on each agent's ownimport_path, so a hand-written mixed multi-agent config stays correct.New example variants
codex-nasde-dev-with-archandgemini-nasde-dev-with-archexercise[[skill]]by-reference (→ live.claude/skills/nasde-dev) on a non-Claude agent — a permanent repro + skill-migration demo. (Also adds the long-missing Codex variant tonasde-dev-skill.)Verification
codex-nasde-dev-with-archwith[[skill]],--without-eval): the trial'strial.logshows Harbor's native registration command running —mkdir -p $HOME/.agents/skills && cp -r /harbor/skills/* $HOME/.agents/skills/— i.e. the[[skill]]dir was uploaded toskills_dirand copied into Codex's native HOME-scoped location insiderun(). The trialconfig.jsonconfirmsskills=[…/nasde-dev](native) with no/app/.agents/skillsinsandbox_files. Under the old code thatcpwould copy from an empty/harbor/skills(the skill went only to/app/.claude/skills, which Codex never scans). The agent turn itself then hit the Codex Plus usage limit — an account constraint, not the fix. (Gemini's nativeactivate_skillregistration was proven the same way in fix(skills): register Codex/Gemini skills natively, not in /app cwd (ADR-012) #65; the mechanism is shared.)pytest— 397 passed (newcollect_*, union, branch, stale-drop, handwritten, basename-collision, frontmatter, mixed-agent tests)ruff check/ruff format --check/mypy— cleanDocs
CHANGELOG.md(Unreleased → Fixed),CLAUDE.md,nasde-benchmark-creatorskill🤖 Generated with Claude Code