capabilities.lock

When you run capa install, CAPA resolves every GitHub or GitLab skill and plugin to an exact commit SHA and writes those SHAs into a lockfile next to your capabilities file. The next install reuses those SHAs so the build is reproducible and (usually) offline-fast.

Why a Lockfile?

Without a lockfile, every install asks Git or GitLab "what does main point to right now?" The answer drifts between teammates and between CI runs. The lockfile pins each declared source to the exact commit you installed last time, which buys you three things:

  • Reproducibility: every capa install across machines and CI hits the same upstream commit until you intentionally update the lock.
  • Speed: if a locked SHA is already in the on-disk cache, CAPA skips the clone.
  • Auditability: the locked SHA is the answer to "what code did we actually install?"

File Location and Format

The lockfile sits next to your capabilities file and matches its format, so diffs stay readable in version control:

  • capabilities.yamlcapabilities.lock (YAML body)
  • capabilities.jsoncapabilities.lock (JSON body)
version: 1
generator: capa@1.7.1
generatedAt: 2026-05-14T18:50:00.000Z
skills:
  - id: web-researcher
    source: github
    repo: vercel-labs/agent-skills
    skillName: web-researcher
    requestedVersion: null
    requestedRef: null
    resolvedRef: 9a3f1cb0e7d4e9f2c8a7b6d5c4b3a2918e7f6d5c
    resolvedVersion: v1.4.2
plugins:
  - id: notion-tools-7f9c1ab
    source: github
    repo: my-org/notion-plugin
    uri: github:my-org/notion-plugin:v2.0.0
    requestedVersion: v2.0.0
    requestedRef: null
    resolvedRef: 7f9c1ab3e8d2c4b5a6e7f8d9c0b1a2e3f4d5c6b7
    resolvedVersion: v2.0.0
    manifestName: notion-tools
    manifestVersion: 2.0.0
{
  "version": 1,
  "generator": "capa@1.7.1",
  "generatedAt": "2026-05-14T18:50:00.000Z",
  "skills": [
    {
      "id": "web-researcher",
      "source": "github",
      "repo": "vercel-labs/agent-skills",
      "skillName": "web-researcher",
      "requestedVersion": null,
      "requestedRef": null,
      "resolvedRef": "9a3f1cb0e7d4e9f2c8a7b6d5c4b3a2918e7f6d5c",
      "resolvedVersion": "v1.4.2"
    }
  ],
  "plugins": [
    {
      "id": "notion-tools-7f9c1ab",
      "source": "github",
      "repo": "my-org/notion-plugin",
      "uri": "github:my-org/notion-plugin:v2.0.0",
      "requestedVersion": "v2.0.0",
      "requestedRef": null,
      "resolvedRef": "7f9c1ab3e8d2c4b5a6e7f8d9c0b1a2e3f4d5c6b7",
      "resolvedVersion": "v2.0.0",
      "manifestName": "notion-tools",
      "manifestVersion": "2.0.0"
    }
  ]
}

Fields

Top level

  • version: lockfile schema version (currently 1).
  • generator: capa version that wrote the file (e.g. capa@1.7.1).
  • generatedAt: ISO 8601 timestamp of the last write.
  • skills: array of locked github/gitlab skill entries.
  • plugins: array of locked remote plugin entries.

Skill entry

  • id: skill ID from your capabilities file.
  • source: github or gitlab.
  • repo: owner/repo path.
  • skillName: the right-hand side of the repo string (the basename or path).
  • requestedVersion: version or tag you asked for in capabilities, or null.
  • requestedRef: commit SHA you asked for, or null.
  • resolvedRef: the full commit SHA actually installed.
  • resolvedVersion: tag the resolved SHA corresponds to (when discoverable), or null.

Plugin entry

Same fields as skills, plus:

  • uri: plugin URI as written in capabilities (e.g. github:owner/repo:v1.0.0).
  • manifestName: plugin name from the manifest.
  • manifestVersion: version from the manifest, when present.

Lifecycle

  1. First install. Run capa install. For each remote source, CAPA clones the repo, resolves the requested ref to a SHA, and writes the lockfile.
  2. Subsequent installs. CAPA reads the lockfile, looks up the resolved SHA for each entry, and reuses the cached snapshot. No git fetch needed when the cache already has that SHA.
  3. You change a version. If you bump the version in capabilities (e.g. my-org/repo@skill:v1.2.0 to my-org/repo@skill:v1.3.0), the requested fields no longer match the lock entry and CAPA re-resolves.
  4. You add a new entry. CAPA resolves it normally and appends to the lockfile.
  5. You remove an entry. CAPA prunes the dropped entry on the next install. If the file ends up empty, CAPA deletes it for you.

Updating to the Latest Code

To pull the latest commit for unpinned (or floating-ref) sources, bypass the lockfile with --no-cache:

capa install --no-cache

CAPA re-resolves every remote source, rewrites the lockfile, and refreshes cache snapshots. Use this when you want to upgrade dependencies tracking main or any other moving ref.

Committing the Lockfile

Commit capabilities.lock to version control. It is what makes teammates and CI install the same code you did. Treat it like package-lock.json or poetry.lock:

  • Commit it alongside changes to capabilities.{yaml,json}.
  • Resolve lockfile merge conflicts by re-running capa install.
  • Read lockfile diffs in PRs. A bumped SHA is an explicit signal that you upgraded a dependency.

Related Documentation