Summary
skills install --frozen-lockfile could not restore a GitHub skill after its upstream repository changed. The project lock stored only ref (a branch or tag name) and computedHash. A restore fetched the current tip of ref, its hash no longer matched, and the install failed. There was nothing in the lock to fetch the old version with.
Lock entries for Git sources now record the commit the skill was installed from:
addrecords it. Clone path:getHeadCommiton the clone. Blob fast path: newfetchTreeCommitinsrc/blob.tsreads the branch tip from the GitHub commits API. It returns the commit only if it is still the commit the tree was read from, so a branch that moved between the two requests records no commit instead of a wrong one.install(frozen or not) fetches<source>#<commit>. This uses the existing commit-SHA clone fallback incloneRepo. The lock still records the originalref, soupdatekeeps tracking the branch, and a non-frozen restore leaves the lock byte-identical.updatemovescommitforward when it reinstalls a skill. It also addscommitto an older entry that has none, when that entry's content still matches.checkwrites nothing.
The global lock (~/.agents/.skill-lock.json) records commit too. add -g writes it, and update -g moves it forward. A failed commit lookup clears it instead of leaving an old one. The global lock has no restore command, so for now the field is a record of what is installed.
The field is optional in both locks, so older locks keep working. A project entry without commit follows ref as before until the next update. Neither lock version changes.
src/blob.ts refactor: the anonymous -> token -> gh api fallback that fetchRepoTree used is now a generic fetchFirstGitHubApi. fetchTreeCommit uses it too. The order of requests and the rate-limit memo are unchanged (the existing blob-fetch-tree-auth tests pass).
Note: when called with a branch name, the GitHub Trees API returns the commit SHA as sha, not the root tree SHA. fetchTreeCommit accepts a match on either.
- refs #500 (item 4, "Commit SHA Tracking". The issue's other items, such as
ciandverify, are out of scope.) - refs #11 (the versioning RFC proposes a
commitfield in the lock)
Related, already closed: #2279 (restoring entries whose ref is a commit SHA; this PR reuses that fallback) and #2560 (lockfile reconciliation tracking issue).
User prompts
Model: Claude Opus 5.5 in Claude Code (Paseo).
- Asked how a frozen lockfile from GitHub recovers from a hash mismatch when upstream changes, and whether the git SHA is tracked.
- After hearing that it does not, asked to store the resolved commit SHA and restore from it, and to open a PR.
- Asked to record the commit in the global lock too, in this PR.
Verification
pnpm vitest run tests/lock-commit-pin.test.ts: 4 passed. These tests use a real local Git repo, reached through anhttps://...gitURL thaturl.<base>.insteadOfrewrites. They cover: add records the commit; after upstream moves on, frozen and non-frozen restores install the old content and leave the lock unchanged; update moves the commit forward; update adds a commit to an old entry that has none;add -grecords the commit andupdate -gmoves it forward. Withsrc/reset tomain, all 4 fail. The global case also fails with only the project-lock commit applied.pnpm vitest run tests/blob-fetch-tree-auth.test.ts: 15 passed, including 4 newfetchTreeCommitcases (tree match, commit-SHA match, branch moved,HEAD).pnpm test: 1376 passed, 4 failed. The 4 failures (src/detect-agent.test.tsx3,src/use.test.tsx1) also fail onmainin this environment. They come from the agent-detection environment variables and a NodeUNDICIwarning on stderr.pnpm type-check,pnpm format:check,pnpm build: pass. The tsnapi snapshot is unchanged.- Run by hand against live GitHub:
add vercel-labs/agent-skills --skill web-design-guidelines(blob path) recordedcommit: 1a2c79d..., which matchesgit ls-remote ... HEAD. After deleting.agents,install --frozen-lockfileprintedRestored web-design-guidelines.add https://github.com/anthropics/skills --skill pdf(clone path) recorded a commit, and a frozen restore succeeded.add -g vercel-labs/agent-skills --skill web-design-guidelinesrecorded the same commit in the global lock.- With a made-up commit in the lock, the restore fails with a clone error, as expected.
- Not verified: GitLab and self-hosted servers. Fetching by SHA needs
uploadpack.allowReachableSHA1InWant(or protocol v2). GitHub and GitLab.com support this.
Two existing mocks of src/git.ts and one child_process mock needed a getHeadCommit stub. Those test files have a one-line change each.
Written by an AI agent (Claude Code). The user asked the question and requested this change. They have not reviewed the diff yet.