Publish a change to your Follower Machines
Check and publish a Machine Profile change on the Source Machine, apply it on Follower Machines, add tools, and roll content back.
Follower Machines never read files from the Source Machine's disk. They receive
only what you publish as a signed Profile Revision, so editing
~/.claude/CLAUDE.md on the Source Machine changes nothing on a Follower
Machine until you publish again. Changed content, an added resource, and a
removal all follow the same steps.
Before you start
- The Source Machine is set up with
canonfig source init. Publishing without it fails with "the profile revision could not be signed (operation="load source credentials"); run 'canonfig source init' first". - You have a Machine Profile file. The examples use
~/canonfig.profile.jsoncfrom Sync your first two machines. The profile file reference lists every field. - Follower Machines fetch from the Source Machine, so it must be serving when they sync. See Run Canonfig unattended.
Publish a change
-
Edit the source file, for example
~/.claude/CLAUDE.md, or the profile file itself. Config values sit in the profile underspec.keys. -
Check the profile on the Source Machine:
canonfig source digest --profile-file ~/canonfig.profile.jsoncsource.digest completed profile: workstation content digests (2): file claude-instructions 2acae736aaa9c6a866fcad37170b4069f9f3e90c3cfec7437ce5d74799fec7b0 skill claude-review-skill 5c21ab53ead1c187aa0ef404c98191621b46ccc37ef1ccc23e49ef1b42ed2528source digestreads everysourcefile exactly as publishing does and prints each resource's content digest. It does not sign or publish anything. Add--resource claude-instructionsto check one resource. -
Publish:
canonfig source publish --profile-file ~/canonfig.profile.jsonc --reviewer "$USER"source.publish completed profile: workstation, sequence 2 revision: workstation:25f4e71bd8dc2ccc11604f4da39a0c7e45e1a0f6809b75a53d378efb73ba43cc published at: 2026-11-02T19:47:11.383Z resources (2): file claude-instructions → ~/.claude/CLAUDE.md skill claude-review-skill → ~/.claude/skills/code-review--revieweris required and records who approved the Profile Revision.
If the content is identical to the latest Profile Revision, source publish
returns that Profile Revision with the same sequence number, and nothing
reaches Follower Machines. Any other content becomes the next sequence number.
Each Follower Machine applies the Profile Revision at its next scheduled run. To apply it immediately, continue with the next section.
Apply the change on a Follower Machine immediately
On the Follower Machine:
canonfig sync --plan
canonfig sync --applyThe plan lists every action, such as write-file or write-config with the
keys it sets, and anything it will remove. sync --plan changes nothing.
After sync --apply reports Converged, canonfig status shows the applied
Profile Revision.
If a resource needs a person on the Follower Machine, for example a login, a
skill edited on that machine, or a config file that does not parse, the plan
and the result name the resource and the reason. The apply stops at that
resource: resources before it stay applied and later ones wait. Fix it, then
run canonfig sync --apply again. See
Recover from a failed or blocked sync.
Add, change, or remove a resource
To add a resource, add an entry to resources with a unique id, then check
and publish as above. To remove one, delete its entry and publish.
| Change on the Source Machine | What a Follower Machine does at its next apply |
|---|---|
Added or changed file (policy replace) | Writes the file. A local edit on the Follower Machine is overwritten. |
Added or changed config keys (policy merge) | Sets the keys the profile declares and leaves every other key in the file alone. |
| Config key dropped from the profile | Removes that key, unless the Follower Machine claimed it with a Local Overlay. |
Added or changed skill (policy replace-if-unmodified) | Updates the skill if nobody changed it on the Follower Machine. A skill edited on the Follower Machine is kept and reported as Follower Drift (exit 4). |
| Resource removed from the profile | Removes the file, keys, or skill it wrote, but only if they still match what it last applied. A copy changed on the Follower Machine stays. |
tool or credential resource removed | Nothing. Canonfig never uninstalls a tool or deletes a local credential. |
To keep a config key on one Follower Machine only, claim it there as a Local Overlay:
canonfig overlay set claude-settings --target ~/.claude/settings.json --key permissions.allowFiles and skills cannot be claimed this way.
To remove skill resources whose source directory you deleted on the Source Machine, see Remove deleted skills from the profile.
Add a tool
Add a tool resource with one Installation Recipe per platform and an exact
version:
{
"id": "typescript",
"kind": "tool",
"target": "tsc",
"spec": {
"kind": "tool",
"toolId": "typescript",
"recipes": [
{ "platform": "linux", "method": "npm", "package": "typescript", "version": "5.6.3" },
{ "platform": "macos", "method": "npm", "package": "typescript", "version": "5.6.3" }
]
},
"verify": { "method": "executable-present", "executable": "tsc" }
}Use the package name and the version you tested on the Source Machine. Every
automatic installer recipe (npm, pnpm, bun, brew, apt, winget, uv, cargo)
needs an exact version. Publishing refuses a recipe without one.
When the tool is missing on a Follower Machine, sync --apply installs it with
the recipe for that Follower Machine's platform, then runs the verification.
This example has no Windows recipe, so a Windows Follower Machine reports
Human Action Required for typescript.
How Canonfig installs and verifies tools
covers recipe fields and how verification finds executables.
To find which tools your configuration files reference, scan them:
canonfig source scan --file ~/.codex/config.toml --file ~/.claude.jsonThe scan proposes tool resources only, never file, config, or skill
resources. Each tool is accepted or needs-review, and the output gives the
reason for each needs-review entry. Use the result to write tool resources
yourself.
source publish --proposal <file> can also add a scanned file's accepted
tools to the profile at publish time. It stops with Human Action Required
(exit 3) when anything in that file needs review, so writing the resource
yourself is the path that always works.
Roll back to earlier content
No command moves Follower Machines back to an earlier sequence number. Publish the earlier content again instead: it becomes the next sequence number, and Follower Machines move forward to it.
This needs the earlier content. Keep the profile and copies of the managed
files in a Git repository, for example ~/canonfig-profile, with sources such
as claude/CLAUDE.md relative to ~/canonfig-profile/profile.jsonc:
mkdir -p ~/canonfig-profile/claude
cp ~/.claude/CLAUDE.md ~/canonfig-profile/claude/CLAUDE.md
git -C ~/canonfig-profile add -A
git -C ~/canonfig-profile commit -m "Update CLAUDE.md"To roll back the last change:
git -C ~/canonfig-profile checkout HEAD~1 -- .
canonfig source digest --profile-file ~/canonfig-profile/profile.jsonc
canonfig source publish --profile-file ~/canonfig-profile/profile.jsonc --reviewer "$USER"This layout also lets you review a change before you publish it. Copy files
into the repository; do not symlink them. A symlink to ~/.claude/CLAUDE.md
resolves outside ~/canonfig-profile, and publishing refuses it.
With the profile in your home directory instead, as in the tutorial, each
source points at the live file. You skip the copy step, but you publish
whatever is on the Source Machine's disk at that moment, including unfinished
edits.
If something fails
Unless a row says otherwise, each refusal exits 2 before anything is signed,
and source digest reports the same problems.
| Message | Cause | Fix |
|---|---|---|
| "resource claude-instructions cannot publish source .claude/CLAUD.md: /home/alex/.claude/CLAUD.md cannot be resolved (ENOENT); a source must exist inside the profile directory /home/alex, ..." | The source file does not exist. | Correct the path; it is relative to the profile file's directory. |
| "resource claude-instructions cannot publish source ../shared/CLAUDE.md: ".." segments are not allowed; keep the source inside the profile file's directory" | The source path leaves the profile file's directory. | Move the file inside the profile file's directory. |
| "resource claude-instructions cannot publish source CLAUDE.md: /home/alex/canonfig-profile/CLAUDE.md resolves to /home/alex/.claude/CLAUDE.md, outside the profile directory /home/alex/canonfig-profile; copy the content into the profile directory, or author an intentional link as "symlinkTo"" | The source is a symlink to a file outside the profile directory. | Copy the file in. For a link that should exist as a link on Follower Machines, use symlinkTo. |
| "the profile cannot be published: resource claude-instructions declares verify.digest 0000…, but its published content has digest 2acae736…. Set verify.digest to 2acae736…, or omit it and publish computes it" | The digest in the profile does not match the content. source digest marks this resource MISMATCH and exits 0. | Remove digest and keep "verify": { "method": "digest" } so publishing computes it. |
| "authored profile file is malformed or invalid: profile.resources[0].kind: Expected a matching schema" | A field is wrong. The message names it by its position in the file. | Fix that field. |
| "authored profile file is malformed or invalid: profile.resources[0].spec.recipes[0].version: Expected npm-family registry installs require an exact version or a reviewed tarball with integrity" | A tool recipe has no exact version. | Add one. |
| "authored profile file /home/alex/canonfig.profile.jsonc could not be read: ENOENT: no such file or directory, ..." | The --profile-file path is wrong. | Correct the path. |
| "nothing to publish: the profile declares no resources and no proposal was given. ..." | The profile has an empty resources list. | Add resources, or pass --allow-empty if Follower Machines should receive an empty Profile Revision. |
| "... cannot be carried exactly: integers must be between -9007199254740991 and 9007199254740991 and numbers must be finite. Canonfig does not round; ..." | A config value is a number Canonfig cannot carry without rounding. | Write it as a string if the program that reads it accepts one. |
| "Missing required option: --reviewer" | --reviewer is required. | Add --reviewer "$USER". |
"the proposal has evidence that needs review: ..." (--proposal only, exit 3) | A tool in the scanned file needs review. | Write those tools as resources in the profile, or drop --proposal. |
Sync your first two machines
Publish your Claude Code and Codex setup from one machine and keep a second machine in sync with it every day.
Remove deleted skills from the profile
Remove skill resources from the Machine Profile file when you delete their directories on the Source Machine, once or while you work.