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.
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. |
What it reads
Section titled “What it reads”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.
$ palm check✓ manifest and lock agree✓ palm.yaml targets match the lock✓ every entry is rendered✓ every entry is on all its targetsx 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=…Options
Section titled “Options”| 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.
The checks
Section titled “The checks”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.
Strict mode
Section titled “Strict mode”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.
palm check --strictA 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.
Acknowledge a warning
Section titled “Acknowledge a warning”A warning you have read and accept goes into the ignore: list of palm.yaml, and palm check stops printing it.
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).
Local sources outside the project
Section titled “Local sources outside the project”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.
Network
Section titled “Network”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.
$ 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": []}Reads and writes
Section titled “Reads and writes”| 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 |
Exit codes
Section titled “Exit codes”| 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. |