Skip to content

Bug: skills orchestrator installs fetched skills into repo tree instead of temp directory and fails to load those skills in the deep agent #179

Description

@danchild

Summary

When the worker fetches project-specific skills at workflow start (via ensure_skills), the installed skill files and skills.lock are written into the forge repo's own skills/ directory. These are runtime artifacts that only need to exist long enough to be bind-mounted into the container — they should never touch the repo tree.

Additionally, even though skills were being fetched from Jira project properties successfully, the dynamic (runtime-fetched) skills were not being mounted into the deep agent container. Only skill_0 (default) and skill_1 (project-specific static) were mounted — the fetched skills never made it into the container because resolve_skill_paths did not know about the temp install directory.

Root Cause

The skills fetch pipeline has two stages:

  1. Clone (fetcher.py) — clones the skill repo into a tempfile.mkdtemp() directory. This is correctly ephemeral.
  2. Install (orchestrator.py) — copies files from the temp clone into skills_dir/{project}/ and writes skills_dir/skills.lock. The temp clone is then deleted.

The problem is in step 2. skills_dir defaults to "skills/" (a relative path in config.py:254), which resolves to the repo's own skills/ directory. The installer copies fetched skills out of the temp dir and into the tracked tree, even though the only downstream consumer (runner.py:185-219) just needs a host path to bind-mount into the container.

A second problem: runner.py called resolve_skill_paths without passing the skills_install_dir, so even when skills were fetched and installed to the temp directory, the resolver only searched the static skills/ tree. The container ended up with only skill_0 and skill_1 mounts — the dynamically fetched skills (e.g. from Jira project properties) were silently missing.

Call Chain

  1. worker.py:243-244 — calls ensure_skills(project_key, jira_client, Path(settings.skills_dir))
  2. orchestrator.py:116_install_entry copies from temp clone into skills_dir/{project}/
  3. orchestrator.py:74,141 — reads/writes skills_dir/skills.lock
  4. runner.py:198-215_get_skill_mounts resolves skill dirs under skills_dir and bind-mounts them into the container

Impact

  • Untracked files pollute the developer's working tree when running the worker locally
  • Risk of accidentally committing fetched third-party skill content to the forge repo
  • skills.lock appears as noise in git status
  • Jira-configured skills were fetched but never loaded into the deep agent — the container only received static skills (skill_0, skill_1), not runtime-fetched ones (skill_2)

Fix

Install fetched skills into a temp directory (following the project convention of tempfile.mkdtemp(prefix="forge-skills-...")) instead of the repo's skills/ directory. The fetched skills only need to live long enough to be bind-mounted into the container. The source-tracked skills/default/ stays in the repo as-is; the resolver just needs to look in two places — the repo for defaults, the temp dir for project overrides.

Pass skills_install_dir through to resolve_skill_paths in the runner so the dynamic skills are included in the container mounts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions