Skip to content

feat(artifact-delivery): follow symbolic links in a Skill folder - #512

Merged
harlan-zw merged 4 commits into
fix/artifact-sizefrom
fix/artifact-symlinks
Oct 7, 2026
Merged

harlan-zw merged 4 commits into
fix/artifact-sizefrom
fix/artifact-symlinks

Conversation

@harlan-zw

Copy link
Copy Markdown
Collaborator

Stacked on #510.

🔗 Linked issue

Related to #510.

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

108 Skills failed every run with INVALID_SOURCE: The Skill source layout was rejected., because their Skill folder holds a symbolic link. 103 of them are simota/agent-skills/*, where each Skill links the shared _common folder at the Repository root.

A build now resolves each link at the same commit and packs the files it names at the link path, as regular files. Each file keeps its own Git blob SHA from the tree, so the build checks its bytes like any other file. The signed statement stays a plain file list, so released CLIs need no change.

Architecture: load() now runs followSymbolicLinks, which reads link blobs and trees from GitHub REST, before selection, the archive reader, and the checks

The rules, recorded in ADR-0014:

Link Result
Ends at a file or folder in the Repository Followed. A folder becomes every file in it, under the link path.
Absolute, past the Repository root, into .git, missing, into a submodule, looping, or through more than 8 links Refused, with one finding each, such as escape -> ../../../outside: the target is outside the Repository
A folder above the Skill folder that holds the link Refused
A folder inside the Skill that holds the link, such as atlas/reference/atlas -> ../../atlas Left out. The Skill already packs those files.
Inside a followed folder Left out, without a read

Following every link the way tar --dereference does cannot work here. On simota/agent-skills, tar recursed 5,800 folders deep and crashed, because _common links every Skill folder back. Refusing the self links instead of leaving them out would keep 22 simota Skills refused.

A new symbolic-links check result lists each link, as _common -> ../_common or with the reason it was left out. It is not required, so released CLIs accept it, as they did omitted-files in #487. A file a link put in the Skill never becomes a linked file: the CLI would read it at the link path, where GitHub serves no file.

The archive lists a link target at its own path, so files no longer arrive in Artifact order. The reader holds entries that pass before their turn, up to 8 MiB a pass. A link file waits for a later target when the files it holds back fit, and reads it from raw.githubusercontent.com otherwise. A Skill without links streams as before.

Private builds follow links by the same rules. The reads the links cost come out of the 900-file limit, so a private build stays inside its subrequest budget.

The sweep harness built the 108 Skills on this branch, with the archives cached locally:

Before After
Ready 0 107
Refused 108 1
Files read from raw.githubusercontent.com 2
Slowest build 1.1 s
Most memory one build added 11.5 MiB

The one still refused is oaustegard/claude-skills/agent-routing. Its only link, agent-routing -> ../../agent-routing, leaves the Repository. garrytan/gstack builds with 1,134 files left out under the 2,000-file limit from #510.

The policy moves to 2026-10-07.4. The signer still signs 2026-10-07.3, which refused every link, so a ready build under it is checked again from its stored bytes. A stored build that followed a link always loads from GitHub, because its bytes do not show which files a link put there.

GLOSSARY.md gains symbolic link, kept apart from ADR-0013's linked file.

skilld update reads an installed copy back as a local Skill and refuses one over 512 files. simota/agent-skills/hone packs 527 files and garrytan/gstack 2,000. A run is unaffected.

🤖 AI disclosure: Harlan Agent Kit modified this description. My AI open-source policy.

108 Skills failed every run with INVALID_SOURCE because their folder held
a symbolic link. 103 are simota/agent-skills, whose Skills link the shared
_common and _templates folders at the Repository root.

A build now resolves each link at the same commit by POSIX rules and packs
the files it names at the link path, each checked against its own Git blob
SHA. Links that leave the Repository, dangle, loop, chain past 8, or point
into .git still refuse the Skill. A link inside a followed folder, and a
link to a folder inside the Skill that holds it, are left out: tar
--dereference recursed 5,800 folders deep on simota/agent-skills.

The non-required symbolic-links check lists every link. Policy 2026-10-07.4
keeps 2026-10-07.3 signable, since that policy refused every link.
Names symbolic link in the glossary, apart from ADR-0013's linked file.
@harlan-zw
harlan-zw added this pull request to stack #513 October 6, 2026 17:22
@harlan-zw harlan-zw added the harlan-agent-review Pull request triage requires an adversarial Review. Adding this label manually always forces one. label Oct 6, 2026
@harlan-github-agent harlan-github-agent Bot added harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. harlan-agent-running An Agent holds a Task on this issue or pull request right now. and removed harlan-agent-review Pull request triage requires an adversarial Review. Adding this label manually always forces one. labels Oct 6, 2026
@harlan-github-agent

harlan-github-agent Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

🤖 MERGED

Harlan Agent Kit posted this automated review. It is not Harlan's personal review or approval. AI open source policy. Last updated: 2026-10-07 02:10 UTC.

GitHub merged this pull request.

  • Logged (15/100): The pull request's only architecture diagram is inaccessible, leaving the review without the visual evidence the description cites.
  • Logged (5/100): An empty link target is misclassified as a link that holds itself instead of being refused as an invalid path. View code
  • Logged (5/100): Whole-Skill limit refusals carry an empty findings list and followed-folder overflows report the wrong folder, so the CLI's refusal notes diverge from the ADR's per-link findings promise. View code

8ef7794b-32d7-4859-a4f5-1205c08502af

@harlan-github-agent harlan-github-agent Bot added harlan-agent-ready The automated Review passed every gate on this head commit. and removed harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. labels Oct 6, 2026

@harlan-github-agent harlan-github-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Harlan Agent Kit posted this automated review. AI open source policy.

Automated Review: READY.

Read the Review results

harlan-zw added a commit that referenced this pull request Oct 6, 2026
#510 adds migration 0145 and ADR-0013, and #512 adds ADR-0014. This
branch moves to 0146 and ADR-0015 so all three merge without a clash.
@harlan-zw
harlan-zw merged commit afb374d into main Oct 7, 2026
6 checks passed
harlan-zw added a commit that referenced this pull request Oct 7, 2026
* feat(registry): move Repositories that GitHub renamed or transferred

ADR-0002 kept a moved Repository under its old name, so pages, sitemaps
and run commands named Repositories GitHub no longer serves, and two
renames left duplicate Repositories in the registry. Sync now follows
GitHub: it stores the Repository ID, moves every row to the new name in
one batch, and keeps the old name as an alias that answers 301.

ADR-0013 replaces ADR-0002.

* chore(scripts): add the moved Repository backfill

Sync moves a Repository the next time it reads it. The script runs the
same statements now for the 13 moves the 2026-10-07 run sweep found, with
a read-only dry run as the default.

* fix(registry): renumber the moves migration and parse the ID once

Main gained its own 0144 migration, and two migrations may not share a
number. The batched summary read narrowed the Repository ID with a check
TypeScript could not carry into the parser, so the parser now owns it.

* fix(registry): queue moved Repositories past the sync prefetch

The hourly prefetch records an unchanged Repository in place without a
sync job. A moved Repository with an unchanged tree never reached
syncRepo, so its rows would never move.

* fix(registry): refuse a move into a name another Repository holds

* fix(scripts): refuse a backfill merge into another Repository's row

The backfill now runs the held-row check sync runs, so it never merges
into a row stored for a different Repository ID. It also stops with a
clear message when the D1 lacks the moves migration.

* chore: renumber the repository move migration and ADR

#510 adds migration 0145 and ADR-0013, and #512 adds ADR-0014. This
branch moves to 0146 and ADR-0015 so all three merge without a clash.

* fix(scripts): reserve migration 0145 for pull request 510

---------

Co-authored-by: Harlan GitHub Agent <harlan-github-agent[bot]@users.noreply.github.com>
@harlan-github-agent harlan-github-agent Bot removed the harlan-agent-ready The automated Review passed every gate on this head commit. label Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant