# How skillFolderHash Detects Skill Updates in the Vercel Skills CLI

> Learn how skillFolderHash detects skill updates in the Vercel Skills CLI by comparing GitHub tree SHAs in .skill-lock.json. Efficiently track changes without downloading files.

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

---

**The `skillFolderHash` is a GitHub tree SHA of the entire skill folder, stored in `~/.agents/.skill-lock.json` and compared against the remote repository to detect changes without downloading individual files.**

The Vercel Skills CLI uses a lightweight, efficient mechanism to determine when installed skills need updates. At the heart of this system is the `skillFolderHash` — a cryptographic fingerprint of the entire skill directory that enables change detection through a single API call. This article explains how the hash is generated, stored, and compared according to the `vercel-labs/skills` source code.

## What Is skillFolderHash and Where Is It Stored

The `skillFolderHash` is the **SHA-256 hash of a GitHub tree object** representing the complete contents of a skill folder. GitHub generates this hash automatically when computing the tree structure of a repository, and it changes whenever any file within that directory is added, removed, or modified.

The CLI stores this hash in a global lock file located at:

```bash
~/.agents/.skill-lock.json

```

Each entry in this file follows the `SkillLockEntry` interface defined in [`src/skill-lock.ts`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts):

```ts
{
  source: string;          // e.g., "vercel-labs/agent-skills"
  skillPath?: string;      // path to folder containing SKILL.md
  skillFolderHash: string; // SHA of that folder in the repo
  ref?: string;          // branch or tag
  // ...
}

```

## How the CLI Fetches and Compares skillFolderHash Values

When you run `skills check` or `skills update`, the CLI executes a three-step comparison process to detect outdated skills.

### Step 1: Read the Local Lock File

The `readSkillLock()` function in [`src/skill-lock.ts`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts) parses `~/.agents/.skill-lock.json` and returns the stored `skillFolderHash` for each installed skill.

### Step 2: Fetch the Latest Remote Hash

The `fetchSkillFolderHash()` function (also in [`src/skill-lock.ts`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts)) retrieves the current hash from GitHub:

```ts
const latestHash = await fetchSkillFolderHash(
  entry.source,      // "owner/repo"
  entry.skillPath!,  // folder path within repo
  token,             // GitHub token for API rate limits
  entry.ref          // branch or tag
);

```

This function delegates to [`src/blob.ts`](https://github.com/vercel-labs/skills/blob/main/src/blob.ts), which implements the GitHub Trees API integration:

```ts
const { fetchRepoTree, getSkillFolderHashFromTree } = await import('./blob.ts');
const tree = await fetchRepoTree(ownerRepo, ref, token);
return getSkillFolderHashFromTree(tree, skillPath);

```

### Step 3: Fetch the Repository Tree

The `fetchRepoTree()` function in [`src/blob.ts`](https://github.com/vercel-labs/skills/blob/main/src/blob.ts) makes a single authenticated request to:

```bash
GET https://api.github.com/repos/{ownerRepo}/git/trees/{branch}?recursive=1

```

The `recursive=1` parameter returns the complete directory structure in one response, including all nested files and subdirectories. Each tree entry contains:

```ts
{
  path: string;   // file or folder path
  mode: string;   // file permissions
  type: "blob" | "tree";  // file or directory
  sha: string;    // object hash
  size?: number;  // bytes (files only)
}

```

### Step 4: Extract the Folder Hash

The `getSkillFolderHashFromTree()` function in [`src/blob.ts`](https://github.com/vercel-labs/skills/blob/main/src/blob.ts) locates the specific skill folder within this tree:

```ts
function getSkillFolderHashFromTree(tree: GitTree, skillPath: string): string | null {
  // Normalize backslashes to forward slashes
  const normalizedPath = skillPath.replace(/\\/g, '/');
  
  // Remove trailing SKILL.md if present
  const folderPath = normalizedPath.replace(/\/SKILL.md$/, '').replace(/SKILL.md$/, '');
  
  // Find the tree entry matching this folder path
  const entry = tree.tree.find(e => e.type === 'tree' && e.path === folderPath);
  
  return entry?.sha ?? null;
}

```

The function returns the `sha` of the tree object representing the skill folder, or `null` if the folder is not found.

## How skillFolderHash Compare Enables Update Detection

The actual comparison occurs in `updateGlobalSkills()` within [`src/cli.ts`](https://github.com/vercel-labs/skills/blob/main/src/cli.ts). The CLI iterates through each installed skill and compares the stored hash against the freshly fetched remote hash:

```ts
// Pseudocode from updateGlobalSkills logic
for (const entry of skillLock.entries) {
  const latestHash = await fetchSkillFolderHash(...);
  
  if (latestHash !== entry.skillFolderHash) {
    // Hash mismatch = folder contents changed
    skillsToUpdate.push(entry);
  }
}

```

When `latestHash !== entry.skillFolderHash`, the CLI knows that **something within that skill folder has changed** — whether a minor documentation edit, a new example file, or a structural refactor. This triggers the update queue.

## Installing Updates and Refreshing the Hash

Once outdated skills are identified, the CLI performs updates by re-invoking the installation command:

```bash
skills add <source> -g -y

```

The `-g` flag ensures global installation, and `-y` auto-confirms the operation. This fresh installation:

1. Downloads the latest skill files
2. Recomputes the `skillFolderHash` from the new tree
3. Writes the updated entry to `~/.agents/.skill-lock.json`

## Practical Example: Checking a Skill's Folder Hash

You can manually verify the hash mechanism using the internal functions:

```ts
import { fetchSkillFolderHash } from './src/skill-lock.ts';

// Check the hash for a specific skill
const ownerRepo = 'vercel-labs/agent-skills';
const skillPath = 'skills/react-best-practices'; // folder containing SKILL.md
const token = process.env.GITHUB_TOKEN; // recommended for API rate limits

const latestHash = await fetchSkillFolderHash(ownerRepo, skillPath, token);
console.log('Current folder hash:', latestHash);

```

Running this outputs the same SHA that appears in your lock file entry. If your local `skillFolderHash` differs, the CLI will flag this skill for update on the next `skills check` run.

## Summary

- **The `skillFolderHash` is a GitHub tree SHA** that fingerprint the entire contents of a skill folder in a single value.

- **Storage location**: `~/.agents/.skill-lock.json`, managed through `readSkillLock()` and `writeSkillLock()` in [`src/skill-lock.ts`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts).

- **Remote fetch**: `fetchSkillFolderHash()` in [`src/skill-lock.ts`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts) delegates to `fetchRepoTree()` and `getSkillFolderHashFromTree()` in [`src/blob.ts`](https://github.com/vercel-labs/skills/blob/main/src/blob.ts), which query the GitHub Trees API.

- **Change detection**: `updateGlobalSkills()` in [`src/cli.ts`](https://github.com/vercel-labs/skills/blob/main/src/cli.ts) compares stored vs. remote hashes; mismatches trigger updates.

- **Efficiency**: One API call yields the entire repository tree, enabling change detection for all installed skills without downloading individual files.

## Frequently Asked Questions

### What exactly does skillFolderHash represent?

The `skillFolderHash` is the **SHA-256 hash of a GitHub tree object** that represents the complete directory structure and file contents of a skill folder. GitHub computes this hash automatically when indexing repository contents. Any change to any file within the folder — including metadata, documentation, or code — produces a different tree SHA, making the hash a reliable change detector.

### Why does the CLI use a tree hash instead of file timestamps or individual file hashes?

The tree hash approach provides **atomic, single-request change detection** across an entire folder. The GitHub Trees API with `?recursive=1` returns every file and subdirectory in one response, eliminating the need for multiple API calls round-trips. This is particularly efficient for skills containing many files. Individual file hashes would require either caching complex state or querying each file separately, while timestamps are unreliable across distributed version control systems.

### Where can I find the skillFolderHash for an installed skill?

The hash is stored in your global lock file at `~/.agents/.skill-lock.json`. Each entry in this JSON file contains a `skillFolderHash` field alongside `source`, `skillPath`, and other metadata. You can view it directly with:

```bash
cat ~/.agents/.skill-lock.json | jq '.[] | select(.skillPath | contains("your-skill-name"))'

```

The CLI reads this value via `readSkillLock()` in [`src/skill-lock.ts`](https://github.com/vercel-labs/skills/blob/main/src/skill-lock.ts) during `skills check` and `skills update` operations.