Summary
When a Skill source directory is a symlink (a very common layout — e.g. ~/.agents/skills/my-skill pointing to a repo checkout elsewhere), skill-up silently installs zero files into the case workspace. The run proceeds, debug logs even print Evaluator: skill installed: my-skill, and the with_skill variant effectively runs without the skill — silently invalidating any with/without benchmark comparison.
Root cause
internal/agent/skill.go → ListSkillFiles walks the source with filepath.Walk(sourceDir, ...). Go's filepath.Walk does not follow a symlinked root: when sourceDir itself is a symlink, the walk visits only the link node. The selector records a single "." entry; installSkill then stats it, sees a directory, skips it — and uploads nothing, with no error.
Reproduction (against current main):
files, _ := agent.ListSkillFiles("/path/to/symlinked-skill", nil, nil)
// => ["."] (1 entry — the link node itself)
files2, _ := agent.ListSkillFiles("/path/to/symlinked-skill/", nil, nil)
// => [".", "SKILL.md", "references", "references/a.md", ...] (correct)
Because resolveSkillConfig builds the source as filepath.Join(skillDir, ref.Path) (never a trailing slash), any skill reached through a symlink hits this path.
Impact
- No error anywhere; the only symptom is an empty
<workspace>/<target>/ directory.
- Benchmark mode reports
with_skill vs without_skill deltas that are actually baseline-vs-baseline. I burned several full eval runs before noticing the agent never saw the skill (verified via the engine's session logs: the skill name never appeared in context).
Suggested fixes (either/both)
- Resolve the source with
filepath.EvalSymlinks before walking (or walk an explicitly resolved root).
- Fail loudly when installation yields zero files — an install that uploads nothing is almost never intentional, and a warning/error would have surfaced this immediately.
Environment
- skill-up
main (commit 4135cc3), go1.25.6 darwin/arm64
- Custom engine (
engine.custom, local transport), environment.type: none
Summary
When a Skill source directory is a symlink (a very common layout — e.g.
~/.agents/skills/my-skillpointing to a repo checkout elsewhere),skill-upsilently installs zero files into the case workspace. The run proceeds, debug logs even printEvaluator: skill installed: my-skill, and thewith_skillvariant effectively runs without the skill — silently invalidating any with/without benchmark comparison.Root cause
internal/agent/skill.go→ListSkillFileswalks the source withfilepath.Walk(sourceDir, ...). Go'sfilepath.Walkdoes not follow a symlinked root: whensourceDiritself is a symlink, the walk visits only the link node. The selector records a single"."entry;installSkillthen stats it, sees a directory, skips it — and uploads nothing, with no error.Reproduction (against current
main):Because
resolveSkillConfigbuilds the source asfilepath.Join(skillDir, ref.Path)(never a trailing slash), any skill reached through a symlink hits this path.Impact
<workspace>/<target>/directory.with_skillvswithout_skilldeltas that are actually baseline-vs-baseline. I burned several full eval runs before noticing the agent never saw the skill (verified via the engine's session logs: the skill name never appeared in context).Suggested fixes (either/both)
filepath.EvalSymlinksbefore walking (or walk an explicitly resolved root).Environment
main(commit 4135cc3), go1.25.6 darwin/arm64engine.custom, local transport),environment.type: none