Local vs. Global Lock File Formats in the Skills CLI: A Complete Technical Guide
The Skills CLI maintains two distinct lock file formats: .skill-lock.json (global, version 3) stores user-wide skill metadata with GitHub tree hashes and timestamps, while skills-lock.json (local, version 1) keeps a minimal, project-scoped manifest with content-derived hashes designed for version control.
The Skills CLI from Vercel Labs uses a dual-lock architecture to manage skill installations across different scopes. Understanding the differences between local and global lock file formats is essential for debugging sync issues, optimizing CI/CD workflows, and contributing to the toolchain. This guide examines both formats through the lens of the actual source implementation in vercel-labs/skills.
File Locations and Discovery
The CLI determines lock file paths through environment-aware resolution functions.
Global Lock Path Resolution
The global lock lives outside any project directory, following XDG conventions when available:
// From src/skill-lock.ts
import { readSkillLock, getSkillLockPath } from '@/skill-lock';
// Returns $XDG_STATE_HOME/skills/.skill-lock.json
// Fallback: ~/.agents/.skill-lock.json
const lockPath = getSkillLockPath();
console.log('Global lock →', lockPath);
const lock = await readSkillLock();
Local Lock Path Resolution
The local lock is anchored to the project root, discovered by walking upward from the current working directory:
// From src/local-lock.ts
import { readLocalLock, getLocalLockPath } from '@/local-lock';
// Searches upward for skills-lock.json
// Default: <project-root>/skills-lock.json
const projectLock = await readLocalLock(); // uses process.cwd()
console.log('Local lock path →', getLocalLockPath());
Schema Versions and Evolution
Both formats share a top-level structure but diverge significantly in version and field depth.
| Aspect | Global Lock (.skill-lock.json) |
Local Lock (skills-lock.json) |
|---|---|---|
| Version | 3 — adds GitHub tree SHA support |
1 — minimal stable schema |
| Schema type | SkillLockFile |
LocalSkillLockFile |
| Timestamp fields | installedAt, updatedAt |
None (intentionally omitted) |
| Remote metadata | sourceUrl, ref, skillPath |
source, ref only |
| State tracking | dismissed, lastSelectedAgents |
None |
The version difference reflects their distinct purposes: the global lock evolves to support richer CLI features, while the local lock prioritizes stability and merge-friendliness.
Hash Computation: Remote vs. Local
The most significant functional difference lies in how each lock verifies skill integrity.
Global Lock: GitHub Tree SHA
The global lock uses remote GitHub API data to identify skill versions. In src/skill-lock.ts, the fetchSkillFolderHash function retrieves the GitHub tree SHA for the entire skill directory:
// From src/skill-lock.ts – fetchSkillFolderHash
import { fetchSkillFolderHash } from '@/skill-lock';
// Calls GitHub Trees API: GET /repos/{owner}/{repo}/git/trees/{tree_sha}
// Returns tree SHA for the skill folder (e.g., "skills/react-best-practices")
const treeHash = await fetchSkillFolderHash(
'vercel-labs/agent-skills',
'skills/react-best-practices',
'main'
);
// Stored in global lock as skillFolderHash
await addSkillToLock('react-best-practices', {
source: 'vercel-labs/agent-skills',
sourceType: 'github',
sourceUrl: 'https://github.com/vercel-labs/agent-skills',
skillPath: 'skills/react-best-practices',
skillFolderHash: treeHash, // GitHub tree SHA
installedAt: Date.now(),
});
This approach is fast and stable — a single API call returns a consistent identifier regardless of local file state.
Local Lock: Content-Derived SHA-256
The local lock computes deterministic hashes from actual file contents. In src/local-lock.ts, the computeSkillFolderHash function walks the skill directory, sorts paths, and hashes content:
// From src/local-lock.ts – computeSkillFolderHash
import { computeSkillFolderHash } from '@/local-lock';
import { join } from 'path';
const skillDir = join(process.cwd(), 'node_modules', 'my-skill');
// SHA-256 computed from:
// 1. All files in directory (recursively)
// 2. Sorted by relative path (deterministic ordering)
// 3. Content of each file fed into hash
const hash = await computeSkillFolderHash(skillDir);
console.log('Local computed hash →', hash);
// Stored in local lock
await addSkillToLocalLock('my-skill', {
source: 'vercel-labs/agent-skills',
sourceType: 'github',
ref: 'main',
computedHash: hash, // SHA-256 of actual files
});
This approach detects any local modification — even uncommitted changes or environment-specific variations.
Functional Purposes and Use Cases
The architectural split serves distinct operational needs.
Global Lock: User-Wide State Management
The global lock in src/skill-lock.ts supports:
- Cross-project skill tracking — skills installed in any project remain visible to the CLI
- Update detection —
skillFolderHashcompared against fresh GitHub tree SHAs - Telemetry and analytics —
installedAt,updatedAt,lastSelectedAgents - Prompt state —
dismissedflags for one-time notifications
Local Lock: Project-Scoped, VCS-Friendly
The local lock in src/local-lock.ts serves:
- Reproducible installations —
skills-lock.jsonchecked into git ensures consistent team environments - CI/CD optimization — minimal file size, no timestamps, deterministic ordering
- Conflict avoidance — hash-based integrity checks prevent merge issues from timestamp churn
- Offline verification — no API calls required to validate local skill state
Integration Points in the CLI
Both lock files are invoked from specific command pathways.
| Command Flow | Primary Lock | Key Functions |
|---|---|---|
experimental_install |
Local | readLocalLock(), addSkillToLocalLock() in src/local-lock.ts |
add (remote install) |
Global | addSkillToLock() in src/skill-lock.ts |
| Update checking | Global | fetchSkillFolderHash() comparing remote vs. skillFolderHash |
| Restore/sync | Local | computeSkillFolderHash() verifying computedHash against disk |
The src/install.ts module specifically handles restoration from local locks, while src/add.ts writes to the appropriate lock based on installation context.
Summary
- Global lock (
.skill-lock.json) — Version 3, user-scoped, stores GitHub tree SHAs, timestamps, and state flags insrc/skill-lock.ts - Local lock (
skills-lock.json) — Version 1, project-scoped, stores content-derived SHA-256 hashes without timestamps insrc/local-lock.ts - Hash strategies — Global uses remote GitHub API calls; local computes deterministic hashes from actual file contents
- Design goals — Global enables cross-project management and update detection; local ensures VCS-friendly, reproducible installations
Frequently Asked Questions
Why does the global lock use version 3 while the local lock remains at version 1?
The global lock in src/skill-lock.ts has evolved to support additional features like GitHub tree SHA tracking, prompt dismissal state, and agent selection history. Each new capability required schema changes, resulting in version 3. The local lock in src/local-lock.ts serves a narrower purpose—recording minimal install state—and has remained stable at version 1 to maximize compatibility across CLI versions and avoid unnecessary migration complexity.
Can I use the local lock without the global lock installed?
Yes. The local lock skills-lock.json is fully self-contained and operates independently. Commands like experimental_install in src/install.ts read exclusively from the local lock to restore project dependencies. However, without the global lock, you lose cross-project skill visibility, automatic update notifications, and the ability to manage skills from outside a project directory.
How does the CLI decide which hash to trust when they differ?
The CLI treats each hash as authoritative for its specific scope. The skillFolderHash in the global lock is compared against fresh GitHub tree SHAs to detect remote updates. The computedHash in the local lock is verified against computeSkillFolderHash() to detect local modifications. When installing, src/add.ts populates both hashes appropriately—remote for global, content-derived for local—ensuring each lock serves its designed purpose without direct conflict.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →