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 — skillFolderHash compared against fresh GitHub tree SHAs
  • Telemetry and analytics — installedAt, updatedAt, lastSelectedAgents
  • Prompt state — dismissed flags for one-time notifications

Local Lock: Project-Scoped, VCS-Friendly

The local lock in src/local-lock.ts serves:

  • Reproducible installations — skills-lock.json checked 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 in src/skill-lock.ts
  • Local lock (skills-lock.json) — Version 1, project-scoped, stores content-derived SHA-256 hashes without timestamps in src/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →