You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A SKILL.md body routinely refers to sibling files by relative path — e.g. "run owl/scripts/check.py", "read owl.md", "apply templates/patch.yaml". Those files ship inside the skill's own directory (skills/<skill>/...) and reach the running agent via COPY . ., but the runtime has no notion of a skill-relative working directory, so those references don't resolve:
CWD is the agent root, not the skill dir. Both cli_execute (cmd.Dir = t.workDir, forge-cli/tools/cli_execute.go:222) and the skill-script executor (forge-cli/tools/exec.go:94) run with CWD = agent working directory. So owl/scripts/check.py (meant relative to skills/owl/) resolves against the agent root and is not found; the real path is skills/owl/scripts/check.py.
Only .sh skill tools are executable.registerSkillTools (forge-cli/runtime/runner.go:3071) resolves only scripts/<name>.sh; a referenced .py/.js/.ts script is neither registered as a tool nor runnable via the bash <script> path.
References are inline in the SKILL.md prose, not declared in frontmatter — so this can't be solved with a metadata field; the harness has to resolve a path mentioned in the body against the skill's directory.
Net effect: an author writes a perfectly reasonable skill ("read owl.md, then run owl/scripts/check.py") and it silently fails at runtime.
Desired behavior
When the agent is following a skill loaded via read_skill, the harness should resolve a relative reference in that skill's instructions against the skill's own directory and act on it:
Read a referenced file — read owl.md / read reference/runbook.md → return the file's contents from skills/<skill>/....
Execute a referenced script — run owl/scripts/check.py → resolve to skills/<skill>/owl/scripts/check.py, pick the interpreter from the extension (.sh→bash, .py→python3, .js→node, .ts→…; forge-core/tools/discovery.go already has langFromExt), run it with CWD set to the skill dir, and honor the skill's declared requires.bins / egress / denied_tools.
Path resolution MUST be confined to the skill's directory (reject .. / absolute escapes), same posture as read_skill's traversal guard and cli_execute's workdir confinement.
Candidate approaches (to decide in design)
Skill-scoped tools — read_skill_file(skill, relpath) and run_skill_script(skill, relpath, args) that resolve against skills/<skill>/, path-guarded, interpreter chosen by extension. Deterministic and explicit about which skill is active.
Skill-relative execution context — carry the "active skill dir" (from the last read_skill) so a plain cli_execute of a relative path resolves against it. Less explicit; needs a clear rule for which skill is active.
Reference rewriting — have read_skill rewrite recognizable relative references in the returned body to agent-root-relative paths. Fragile (prose parsing) — noted for completeness, not preferred.
Whatever the mechanism, it should build on #250, which already (a) makes read_skill resolve a skill by its loadable name and (b) appends a ## Skill files listing of the skill's directory contents (scripts annotated by language, reference files). That listing gives the model the agent-root-relative paths today; this issue closes the loop so skill-relative execution/reading actually works.
Acceptance criteria
A relative file reference in a SKILL.md resolves and reads from the skill's directory (read owl.md, read reference/*.md).
A relative script reference executes with the correct interpreter by extension (.sh/.py/.js/.ts), CWD = skill dir, honoring requires.bins / egress / denied_tools.
Path resolution is confined to the skill directory (traversal / absolute-escape rejected), with a test.
Docs: the Skill Designer prompt (forge-ui/skill_builder_context.go) and skill docs explain that references are skill-relative and which languages are runnable.
Tests covering read + execute for shell/python/javascript and the traversal guard.
Problem
A
SKILL.mdbody routinely refers to sibling files by relative path — e.g. "runowl/scripts/check.py", "readowl.md", "applytemplates/patch.yaml". Those files ship inside the skill's own directory (skills/<skill>/...) and reach the running agent viaCOPY . ., but the runtime has no notion of a skill-relative working directory, so those references don't resolve:cli_execute(cmd.Dir = t.workDir,forge-cli/tools/cli_execute.go:222) and the skill-script executor (forge-cli/tools/exec.go:94) run with CWD = agent working directory. Soowl/scripts/check.py(meant relative toskills/owl/) resolves against the agent root and is not found; the real path isskills/owl/scripts/check.py..shskill tools are executable.registerSkillTools(forge-cli/runtime/runner.go:3071) resolves onlyscripts/<name>.sh; a referenced.py/.js/.tsscript is neither registered as a tool nor runnable via thebash <script>path.Net effect: an author writes a perfectly reasonable skill ("read
owl.md, then runowl/scripts/check.py") and it silently fails at runtime.Desired behavior
When the agent is following a skill loaded via
read_skill, the harness should resolve a relative reference in that skill's instructions against the skill's own directory and act on it:read owl.md/read reference/runbook.md→ return the file's contents fromskills/<skill>/....run owl/scripts/check.py→ resolve toskills/<skill>/owl/scripts/check.py, pick the interpreter from the extension (.sh→bash,.py→python3,.js→node,.ts→…;forge-core/tools/discovery.goalready haslangFromExt), run it with CWD set to the skill dir, and honor the skill's declaredrequires.bins/ egress /denied_tools.Path resolution MUST be confined to the skill's directory (reject
../ absolute escapes), same posture asread_skill's traversal guard andcli_execute's workdir confinement.Candidate approaches (to decide in design)
read_skill_file(skill, relpath)andrun_skill_script(skill, relpath, args)that resolve againstskills/<skill>/, path-guarded, interpreter chosen by extension. Deterministic and explicit about which skill is active.read_skill) so a plaincli_executeof a relative path resolves against it. Less explicit; needs a clear rule for which skill is active.read_skillrewrite recognizable relative references in the returned body to agent-root-relative paths. Fragile (prose parsing) — noted for completeness, not preferred.Whatever the mechanism, it should build on #250, which already (a) makes
read_skillresolve a skill by its loadable name and (b) appends a## Skill fileslisting of the skill's directory contents (scripts annotated by language, reference files). That listing gives the model the agent-root-relative paths today; this issue closes the loop so skill-relative execution/reading actually works.Acceptance criteria
read owl.md,read reference/*.md)..sh/.py/.js/.ts), CWD = skill dir, honoringrequires.bins/ egress /denied_tools.skillEntryHasScript/ the catalogprovides:classification is broadened in lockstep so multi-language tools no longer fall between "not callable" and "in provides" (see the note left on that helper in fix(skills): align skill catalog names with read_skill resolution #250).forge-ui/skill_builder_context.go) and skill docs explain that references are skill-relative and which languages are runnable.Context / related
.sh-only registration:forge-cli/runtime/runner.go:3071,forge-cli/tools/cli_execute.go:222,forge-cli/tools/exec.go:94forge-core/tools/discovery.go(langFromExt)read_skilldirectory listing)