# Local vs. Global Lock File Formats in the Skills CLI: A Complete Technical Guide

> Understand the differences between local (version 1) and global (version 3) lock file formats in the Skills CLI. Learn how each format manages skill metadata and version control.

- Repository: [Vercel Labs/skills](https://github.com/vercel-labs/skills)
- Tags: deep-dive
- Published: 2026-04-23

---

**The Skills CLI maintains two distinct lock file formats: [`.skill-lock.json`](https://github.com/vercel-labs/skills/blob/main/.skill-lock.json) (global, version 3) stores user-wide skill metadata with GitHub tree hashes and timestamps, while [`skills-lock.json`](https://github.com/vercel-labs/skills/blob/main/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:

```typescript
// 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:

```typescript
// 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`](https://github.com/vercel-labs/skills/blob/main/.skill-lock.json)) | Local Lock ([`skills-lock.json`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts), the `fetchSkillFolderHash` function retrieves the GitHub tree SHA for the entire skill directory:

```typescript
// 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`](https://github.com/vercel-labs/skills/blob/main/src/local-lock.ts), the `computeSkillFolderHash` function walks the skill directory, sorts paths, and hashes content:

```typescript
// 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`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main/src/local-lock.ts) serves:

- **Reproducible installations** — [`skills-lock.json`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main/src/local-lock.ts) |
| `add` (remote install) | Global | `addSkillToLock()` in [`src/skill-lock.ts`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts) |
| Update checking | Global | `fetchSkillFolderHash()` comparing remote vs. `skillFolderHash` |
| Restore/sync | Local | `computeSkillFolderHash()` verifying `computedHash` against disk |

The [`src/install.ts`](https://github.com/vercel-labs/skills/blob/main/src/install.ts) module specifically handles restoration from local locks, while [`src/add.ts`](https://github.com/vercel-labs/skills/blob/main/src/add.ts) writes to the appropriate lock based on installation context.

## Summary

- **Global lock ([`.skill-lock.json`](https://github.com/vercel-labs/skills/blob/main/.skill-lock.json))** — Version 3, user-scoped, stores GitHub tree SHAs, timestamps, and state flags in [`src/skill-lock.ts`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts)
- **Local lock ([`skills-lock.json`](https://github.com/vercel-labs/skills/blob/main/skills-lock.json))** — Version 1, project-scoped, stores content-derived SHA-256 hashes without timestamps in [`src/local-lock.ts`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main/skills-lock.json) is fully self-contained and operates independently. Commands like `experimental_install` in [`src/install.ts`](https://github.com/vercel-labs/skills/blob/main/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`](https://github.com/vercel-labs/skills/blob/main/src/add.ts) populates both hashes appropriately—remote for global, content-derived for local—ensuring each lock serves its designed purpose without direct conflict.