Skip to content

palm check

palm check proves that palm.yaml, the lock, the generated files and the in-repo sources agree. It never writes, never prompts and never fetches for an in-repo source.

Terminal window
palm check [--quiet] [--strict] [--json] [--allow-local-sources]
Command What it does
palm check Run every check and print one line per check, then the problems grouped by entity.
palm check --quiet Print only the problems, one line each, and every skipped check. Nothing at all when everything passes.
palm check --strict The same, and exit 1 on a foreign hook or stdio server and on a check that could not run.
palm check --json The same, as one JSON document, with every problem file by file.
palm check -g Check the global scope against ~/.palm/applied.yaml and the harness homes.
palm check --allow-local-sources Check a project whose palm.yaml names a file:// source outside it.

palm check reads palm.yaml, palm.lock.yaml, the files palm wrote, .palm/assets/, the in-repo sources, and the harness files palm merges into or parses. Those harness files are the hook and settings files, such as .claude/settings.json and .codex/hooks.json, and the MCP configs, such as .mcp.json. In them it reports every hook command and stdio MCP server that no lock entry explains, in every event array. It reads nothing else: an agent file or a script you added by hand outside palm’s folders is not its concern.

In a folder without palm.yaml, palm check exits 1 with x no palm.yaml here. It still scans the tracked harness configs, such as .cursor/mcp.json, for secret-shaped literals, so a live key never passes.

~/project
$ palm check
✓ manifest and lock agree
✓ palm.yaml targets match the lock
✓ every entry is rendered
✓ every entry is on all its targets
x 1 generated file differs from the lock
✓ no stray file in a folder palm owns
- applied files outside the lock: skipped (global scope only)
✓ in-repo sources match the lock
✓ no generated path lies inside a source
✓ every program palm installed is trusted
✓ no foreign command in the hook files palm manages
✓ no foreign stdio server in the MCP files palm manages
✓ every hook script exists
✓ no literal secret in generated files
! 1 server needs variables that are not set
✓ generated files are committed
✓ every source is declared
✓ no link leaves the scope
✓ no hidden Unicode in generated files
✓ no harness loads an entity twice
✓ no two agents or commands share a name in a harness folder
✓ every skill an agent preloads is installed
✓ instruction files are within harness limits
x skill tdd: .agents/skills/tdd/references/mocking.md is missing; fix: palm install
! mcp docs: needs DOCS_TOKEN, which is not set; fix: export DOCS_TOKEN=…
$ palm install
↺ skill tdd .agents/skills/tdd/, .claude/skills/tdd/ 5 files (claude gets its own copy in .claude/skills)
1 restored, 1 unchanged.
$ palm check
✓ manifest and lock agree
✓ palm.yaml targets match the lock
✓ every entry is rendered
✓ every entry is on all its targets
✓ generated files match the lock
✓ no stray file in a folder palm owns
- applied files outside the lock: skipped (global scope only)
✓ in-repo sources match the lock
✓ no generated path lies inside a source
✓ every program palm installed is trusted
✓ no foreign command in the hook files palm manages
✓ no foreign stdio server in the MCP files palm manages
✓ every hook script exists
✓ no literal secret in generated files
! 1 server needs variables that are not set
✓ generated files are committed
✓ every source is declared
✓ no link leaves the scope
✓ no hidden Unicode in generated files
✓ no harness loads an entity twice
✓ no two agents or commands share a name in a harness folder
✓ every skill an agent preloads is installed
✓ instruction files are within harness limits
! mcp docs: needs DOCS_TOKEN, which is not set; fix: export DOCS_TOKEN=…
Option Meaning
--quiet print only the problems (nothing when there are none)
--strict fail on foreign hooks and servers and on checks that could not run
--allow-local-sources palm.yaml may name file:// sources outside the project

The global options also apply.

palm runs every check in this order and prints one line each: ✓ passed, x failed, ! warned, or skipped with the reason. A skipped check never shows ✓. Problems follow, one line per entity with a file count, so five missing skills print five lines, not one per file. A hook command edited on disk is one line, under exec-trusted, naming the command. It prints no problems only when nothing failed, nothing warned and every check ran; after a check that could not run it says no problems in the checks that ran; 1 did not run.

Id Fails when Fix
manifest-lock an entry of palm.yaml has no lock entry, or the reverse; a source’s ref: differs from the lock’s; a lock source lacks its URL or commit; a plugin’s only: or exclude: names nothing palm install
targets targets: in palm.yaml differs from the targets the lock rendered, such as codex removed from targets: 88 files will be removed by palm install palm install, after palm install --dry-run lists the removals
render an entry is installed for no target: its render is empty palm install
partial an entry is installed for some of its targets and not the others palm install
lock-disk a listed file is missing or differs from the render; a merged entry is missing or changed palm install to restore, palm install <source> <name> --force for an edited file
orphans a file inside an entity folder palm owns, such as .claude/skills/tdd/, is not in the lock palm install <source> <name> --force removes it
pending under -g: applied files the lock no longer lists (a pull removed their entry) palm install -g removes them
local-sources an entry of an in-repo source has files that differ from its lock content hash; an entity still carrying the TODO: describe placeholder palm create wrote only warns palm install, then commit palm.lock.yaml and the files it re-rendered; write the description
source-paths a path the lock owns reaches into a declared source, for example through a .claude/skills symlink move the source, or remove the link
exec-trusted an entry has a program and no trust:, its program hash is not in its trust:, or a command palm merged was changed on disk palm install --allow-exec <key>=<hash>, or palm install <source> hook:<name> --force
foreign-hooks warning, a failure under --strict: a hook command in any event array of a hook file palm parses that no lock entry explains, such as foreign hook command in .claude/settings.json (SessionStart); script missing when the command names a file that is not there keep it if you added it and acknowledge it in ignore:, else remove it
foreign-servers warning, a failure under --strict: a stdio MCP server in an MCP config palm parses that no lock entry explains keep it if you added it and acknowledge it in ignore:, else remove it
hook-scripts a hook command names a file that is missing or not executable palm install
secrets a generated file, or a harness config palm merges into, holds a secret-shaped literal or a literal env or header value, and git tracks it or others can read it; a literal is blamed on the server at its JSON pointer, and a server palm did not write is called foreign; high-entropy text in code only warns palm install <source> <name> --force after fixing the source
variables warning only: a variable an installed server needs is not set, one line per server export VAR=…
git-ignored a file the lock lists, a file palm merged into, or a file in .palm/assets is ignored by git; one not yet committed only warns and names git add edit .gitignore, or git add
sources-declared a lock source is not in palm.yaml, or an in-repo source is missing on disk palm install, or restore the directory
links an output path resolves outside the scope; a link under an output directory dangles or leaves the scope; a source overlaps an output directory, also through a symlink move the source, or remove the link
hidden-unicode a generated file holds a bidi override, a tag character or a rare variation selector; zero-width characters only warn fix the source, then palm install --force; acknowledge a zero-width warning in ignore:
double-load warning only: a harness would load one entity twice, such as an instruction in both AGENTS.md and .cursor/rules, or a skill installed in the project and with -g; copies palm chose, such as a skill Cursor reads once from .claude/skills, never warn narrow the entry with targets: until 0.3, or remove one of the two installs
agent-names warning only: two agent files in one harness folder carry the same name, so the harness loads one; a command file, such as .cursor/commands/deploy.md, sits beside the skill palm installed from it rename or remove one, or narrow the entry with targets:
preloads warning only: an installed agent preloads a skill or server nothing installs the palm install line the warning names
block-size warning when the managed blocks of a root AGENTS.md or GEMINI.md pass 24 KiB; failure above the harness’s documented cap narrow the entry with targets: until at: works in 0.3

Warnings alone exit 0, and palm check prints them without no problems; under --strict, the foreign-program warnings fail. Commit what palm install wrote before you expect a clean check, since a generated file that git does not track yet is a warning. When one entity is installed in both the project and the global scope, palm check warns, since the harness lists it twice.

palm check --strict exits 1 on a failure, on a hook command or stdio MCP server in a harness file palm parses that no lock entry explains (foreign-hooks, foreign-servers), and on a check that could not run. Use it in CI when a hand-added hook or MCP server, or a check that could not run, must stop the pull request. A check that does not apply, such as pending in a project, stays skipped under --strict.

Terminal window
palm check --strict

A skipped check is never a pass. With --offline and an empty cache, lock-disk prints skipped (cache empty; run palm install), and --strict fails on it. --quiet still prints every skipped check, so a quiet run cannot hide one.

--strict checks what the lock pins, not what is newer upstream. A source pinned to a commit sha passes even when its branch moved on. palm update covers that.

A warning you have read and accept goes into the ignore: list of palm.yaml, and palm check stops printing it.

palm.yaml
ignore:
- hidden-unicode:acme/standards/skills/writing/SKILL.md
- foreign-hooks:.claude/settings.json#Stop
Entry Acknowledges
hidden-unicode:<source>/<path> the zero-width characters in one file of a source
foreign-hooks:<file>#<event> the hook commands in one event array of a harness file that palm did not write
foreign-servers:<file>#<name> one stdio MCP server in a harness config that palm did not write

An entry acknowledges warnings only. A failure, such as a bidi override, never goes away through ignore:, and under --strict an acknowledged foreign program no longer fails. Each warning that ignore: can acknowledge names its entry in the fix. See palm.yaml.

When a skills-lock.json or apm.yml sits in a project with no entries yet, palm check prints one hint: palm 0.3 adds an import command for those files. Checks that need git run only inside a repository. Elsewhere they print skipped (not a git repository).

A palm.yaml that names a file:// source outside the project, such as a mirror on this machine, makes palm check stop with x source "kit" is file:///…, outside the project before it reads anything. A committed palm.yaml must not make palm read a private repository on the machine that clones it. Run palm check --allow-local-sources on purpose: in a clone that uses local mirrors, or in CI on an air-gapped machine. palm install, palm update and palm remove take the same flag; a URL you type on the command line needs none.

lock-disk recomputes each render from the cache. A locked commit that is not in the cache is fetched once, which is the only network access palm check makes. Cache ~/.palm/cache between CI runs, and palm check runs offline. With --offline and a commit missing from the cache, the checks that need the render print skipped (cache empty; run palm install), never a false failure. --strict turns that skip into a failure. A fetch that fails, or a cache palm cannot write to, is reported as that failure, with E_NETWORK or E_IO and the path, never as files that differ.

~/project
$ palm check --json
{
"ok": false,
"checks": [
{
"id": "manifest-lock",
"label": "manifest and lock agree",
"status": "ok",
"problems": []
},
{
"id": "targets",
"label": "palm.yaml targets match the lock",
"status": "ok",
"problems": []
},
{
"id": "render",
"label": "every entry is rendered",
"status": "ok",
"problems": []
},
{
"id": "partial",
"label": "every entry is on all its targets",
"status": "ok",
"problems": []
},
{
"id": "lock-disk",
"label": "1 generated file differs from the lock",
"status": "fail",
"problems": [
{
"entity": {
"kind": "skill",
"name": "tdd",
"source": "mattpocock/skills"
},
"file": ".agents/skills/tdd/references/mocking.md",
"message": ".agents/skills/tdd/references/mocking.md is missing",
"fix": "palm install"
}
]
},
{
"id": "orphans",
"label": "no stray file in a folder palm owns",
"status": "ok",
"problems": []
},
{
"id": "pending",
"label": "applied files outside the lock: skipped (global scope only)",
"status": "skipped",
"problems": []
},
{
"id": "local-sources",
"label": "in-repo sources match the lock",
"status": "ok",
"problems": []
},
{
"id": "source-paths",
"label": "no generated path lies inside a source",
"status": "ok",
"problems": []
},
{
"id": "exec-trusted",
"label": "every program palm installed is trusted",
"status": "ok",
"problems": []
},
{
"id": "foreign-hooks",
"label": "no foreign command in the hook files palm manages",
"status": "ok",
"problems": []
},
{
"id": "foreign-servers",
"label": "no foreign stdio server in the MCP files palm manages",
"status": "ok",
"problems": []
},
{
"id": "hook-scripts",
"label": "every hook script exists",
"status": "ok",
"problems": []
},
{
"id": "secrets",
"label": "no literal secret in generated files",
"status": "ok",
"problems": []
},
{
"id": "variables",
"label": "1 server needs variables that are not set",
"status": "warn",
"problems": [
{
"entity": {
"kind": "mcp",
"name": "docs",
"source": "palm.yaml"
},
"message": "needs DOCS_TOKEN, which is not set",
"fix": "export DOCS_TOKEN=…"
}
]
},
{
"id": "git-ignored",
"label": "generated files are committed",
"status": "ok",
"problems": []
},
{
"id": "sources-declared",
"label": "every source is declared",
"status": "ok",
"problems": []
},
{
"id": "links",
"label": "no link leaves the scope",
"status": "ok",
"problems": []
},
{
"id": "hidden-unicode",
"label": "no hidden Unicode in generated files",
"status": "ok",
"problems": []
},
{
"id": "double-load",
"label": "no harness loads an entity twice",
"status": "ok",
"problems": []
},
{
"id": "agent-names",
"label": "no two agents or commands share a name in a harness folder",
"status": "ok",
"problems": []
},
{
"id": "preloads",
"label": "every skill an agent preloads is installed",
"status": "ok",
"problems": []
},
{
"id": "block-size",
"label": "instruction files are within harness limits",
"status": "ok",
"problems": []
}
],
"warnings": []
}
File Reads Writes
palm.yaml, palm.lock.yaml yes never
generated files, .palm/assets/, in-repo sources yes never
~/.palm/cache/ yes a missing locked commit only
git git ls-files, git check-ignore, git rev-parse never
Code When
0 No check failed. Warnings alone exit 0, except under --strict.
1 At least one check failed; under --strict, a foreign hook or server was found or a check did not run; there is no palm.yaml here; or palm.yaml names a file:// source outside the project without --allow-local-sources.
2 An unknown option, or a palm.yaml in the 0.1 format.