Skip to content

palm.lock.yaml

palm.lock.yaml records what each source resolved to and what palm wrote. palm writes it on install, remove and update, and you commit it with palm.yaml and the generated files.

~/project
$ cat palm.lock.yaml
# palm lockfile: written by palm, do not edit by hand.
version: 3
sources:
mattpocock/skills:
url: https://github.com/mattpocock/skills.git
ref: ^1.2
resolved: v1.2.3
sha: 2c0db9046017ad90f70ed14046665fee10b122b9
descriptor: marketplace
trailofbits/skills:
url: https://github.com/trailofbits/skills.git
ref: ^2.1
resolved: v2.1.0
sha: 924ffa8889fde7dae2bcd6e8f43425240daafa51
descriptor: marketplace
entries:
- kind: skill
name: tdd
source: mattpocock/skills
path: skills/engineering/tdd
content: sha256:e751ad1a5bc7e0600cfc571ddb396bcec66547a02c03ce1d3b8b5bc6d5a2069e
render: {claude: sha256:04476c5e20ef4f78bac75223193a01ac7248dfc993dce7d085160e577436dec8, cursor: sha256:04476c5e20ef4f78bac75223193a01ac7248dfc993dce7d085160e577436dec8}
files:
- .claude/skills/tdd/SKILL.md
- .claude/skills/tdd/references/mocking.md
notes:
- cursor reads .claude/skills; no second copy
- kind: hook
name: gh-cli
source: trailofbits/skills
via: plugin:gh-cli
path: plugins/gh-cli/hooks/hooks.json
content: sha256:3490117f0f0718f891d6ba652d4564e7fb343084f021d1a1ce34b4bff2bd759d
render: {claude: sha256:fba4ddb4f71794f14c6569b9d3dd40009f07cb1a4fa320618d37e640f2143e03, cursor: sha256:cf64b9d3d0d036e334873451b7427c6670e8c2b1e5f4c9cb00dc0a8e9c6e93d6}
files:
- .palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/hooks.json
- .palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/intercept-github-curl.sh
- .palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/persist-session-id.sh
merged:
- {file: .claude/settings.json, at: /hooks/PreToolUse, id: palm:hook:gh-cli:1, key: sha256:b290792c, created: true}
- {file: .claude/settings.json, at: /hooks/SessionStart, id: palm:hook:gh-cli:0, key: sha256:291c1125, created: true}
- {file: .cursor/hooks.json, at: /hooks/preToolUse, id: palm:hook:gh-cli:1, key: sha256:ec2c0c34, created: true}
- {file: .cursor/hooks.json, at: /hooks/sessionStart, id: palm:hook:gh-cli:0, key: sha256:0e246eba, created: true}
exec:
commands:
- {id: PreToolUse//Bash, command: CLAUDE_PLUGIN_ROOT="$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/intercept-github-curl.sh}
- {id: SessionStart//-, command: CLAUDE_PLUGIN_ROOT="$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli bash "$CLAUDE_PROJECT_DIR"/.palm/assets/trailofbits__skills/gh-cli/plugins/gh-cli/hooks/persist-session-id.sh}
closure: {root: .palm/assets/trailofbits__skills/gh-cli, files: 3, tree: sha256:208bc9feb334e591e9ed4f6f9a86c72cb790b6d0af6abc71d8ade7e52a7d7262}
hash: sha256:23759d85ebda016f23c6343d5a49064f98ce3f65bd1d68616ad0a2541ae18adb
trust: [sha256:23759d85ebda016f23c6343d5a49064f98ce3f65bd1d68616ad0a2541ae18adb]
- kind: plugin
name: gh-cli
source: trailofbits/skills
path: plugins/gh-cli
content: sha256:d9c283078a93fdf1cbc152cd638db9343c55162516b53fa52233c02e4a887f3a
render: {}
files: []
deps:
- {kind: hook, name: gh-cli}

The lock is deterministic. Saving the same lock twice gives the same bytes, and a newcomer’s lock for the same palm.yaml equals a veteran’s.

Key Meaning
version 3. Versions 1 and 2 come from palm 0.1, and only palm migrate reads them.
sources one record per source, keyed by the name in palm.yaml
entries one record per installed entity, sorted by kind, then name
Key Meaning
url the clone URL of a git source
root the folder inside the repository, when set
ref the intent, copied from palm.yaml when it was resolved
resolved the tag a range resolved to
sha the resolved commit, the one a bare install uses
path an in-repo source’s project-relative path
layout the layout in effect, so another machine rebuilds the same index from the commit alone
descriptor the scan rule that produced the index: descriptor, apm, marketplace, plugin-manifest or convention

An in-repo source has no commit and no source-wide hash. Each of its entries carries a content hash over that entry’s own files, and palm check compares entry by entry. Two pull requests that change two different skills therefore touch two different lock lines.

Key Meaning
kind, name the entity
source the source name, or manifest for an MCP server declared in palm.yaml
path where the entity lives in the source
via plugin:<name> when a plugin selected it
content sha256 of the entity’s content in the source; for an in-repo source, the signal that its files changed
render one render hash per target: sha256 over the path, mode and content hash of every file the target writes, plus every fragment it merges
files every path palm wrote for the entry, sorted, closure files included
merged the fragments merged into shared files, as { file, at, id, key }
exec, trust the entry’s programs and your consent; see below
notes what install printed once: dropped fields, skipped targets, shared folders; palm describe shows them
targets only when palm.yaml narrows the entry below the scope’s set; a refused target never narrows it
at the placement root from palm.yaml, stored for 0.3
deps plugin entries only: the members the plugin declared

Two hashes per entry per target, never one per file, keep the lock short and let every machine compute the same values.

A fragment in a shared file, such as a hook in .claude/settings.json or a server in .mcp.json, carries two identities.

Key Meaning
file the shared file
at a JSON pointer such as /hooks/Stop, a TOML table such as /mcp_servers/docs, or block:<id> for a Markdown block
id palm’s identity, palm:<kind>:<name>:<n>
key what finds the fragment on disk: the object key, the block id, or a short hash of an array item’s identity

palm tells “changed” (found, different value) from “missing” (not found). Removing one plugin therefore never takes another plugin’s hook, even when both merged into the same array.

An entry with a hook or a stdio MCP server carries its programs.

palm.lock.yaml
exec:
commands:
- { id: Stop//-, command: 'bash "$CLAUDE_PROJECT_DIR"/agent-kit/hooks/quality.sh' }
hash: sha256:5d41c3b0…
trust: [sha256:5d41c3b0…]
Key Meaning
exec.commands one line per command a harness runs: <event>//<matcher or -> for hooks, stdio for a server
exec.closure the scripts the commands run and the files they read, with their count and tree hash; for an in-repo source, hashed where they sit, since they run in place
exec.hash the program hash over the canonical commands, events, matchers, environment keys, working directory and the closure tree
trust the hashes a person consented to; palm installs silently while exec.hash is among them

The lock diff is the team’s consent record, with readable commands and one hash line reviewed together. An entry with a program and no trust:, or with an empty render:, fails palm check. A hook you declined is not in the lock. It is an exclude: on the plugin entry in palm.yaml, where review sees it.

No absolute path, home directory, hostname, timestamp, machine fact, per-file hash, created-folder list, top-level target list, secret value or merged value. The value of a merged fragment is recomputed from the render, and a secret-shaped value appears only as a redacted hash.

~/.palm/palm.lock.yaml has the same shape. Every path starts with a token: <home>, <palm>, <agents>, <claude>, <codex>, <copilot>, <cursor>, <gemini> or <opencode>. The same lock works on machines whose harness homes differ, and ~/.palm/applied.yaml records the real paths this machine holds.