Canonfig
Reference

Profile file reference

Look up every Machine Profile field, resource kind, Apply Policy, and verification method, and the rules publishing enforces.

A Machine Profile is the JSONC file you write on the Source Machine to declare the configuration Follower Machines should have. canonfig source publish --profile-file <file> validates it and signs a Profile Revision, which never changes after publication. Comments and layout in the file do not change the Profile Revision's digest.

Profile file

{
  "version": 2,
  "id": "workstation",
  "name": "Developer workstation",
  "groups": [
    { "name": "developers", "description": "Developer tools" }
  ],
  "resources": [],
  "scheduleDefault": { "type": "daily", "at": "04:30", "timezone": "local" }
}
FieldRequiredValue
versionNoAlways 2. The file format version, not a revision number.
idYesProfile ID that Follower Machines select with --profile.
nameYesDisplay name.
groupsNoFollower Groups: { "name", "description"? }. Resources can target only declared groups.
resourcesNoProfile Resources. Publishing an empty profile needs --allow-empty.
scheduleDefaultNoA suggested schedule; see Suggested schedule.

IDs and group names start with a letter or digit and contain only letters, digits, ., _, :, and -.

Publication assigns each revision the next sequence number. Publishing content equal to the latest revision returns that revision unchanged.

Suggested schedule

scheduleDefault is a suggestion. Sync never installs it. A Follower Machine that has no schedule set sees "schedule available" and accepts it with canonfig schedule set --default.

ShapeExample
Daily{ "type": "daily", "at": "04:30", "timezone": "local" }
Weekly{ "type": "weekly", "days": ["Mon", "Thu"], "at": "12:30", "timezone": "local" }

timezone must be "local": each Follower Machine runs at that time in its own time zone. A Follower Machine that needs a named time zone sets its own schedule with canonfig schedule set --timezone. See Sync on a schedule.

Resource fields

FieldRequiredValue
idYesUnique within the profile.
kindYesfile, directory, config, skill, tool, or credential.
targetYesPath on the Follower Machine (~ is the Follower Machine's home directory), or the command name for a tool. No .. segments or glob characters.
policyNoApply Policy. Defaults by kind; see below.
groupsNoApply only on Follower Machines in one of these Follower Groups.
dependsOnNoResource IDs that must apply first. No cycles.
specYesKind-specific content. Its kind matches the resource kind.
verifyYesHow Canonfig checks the result; see Verification.

Publication refuses duplicate IDs, missing dependencies, dependency cycles, undeclared groups, invalid targets, and a policy or verification method the kind does not allow.

Resource kinds and defaults

KindWhat a Follower Machine getsDefault policyOther allowed policies
fileOne file with exact content and mode, or a symlinkreplacereplace-if-unmodified, append-local
directoryA tree of files and directoriesmirror-ownedreplace
configDeclared keys inside a JSON, TOML, or YAML filemergereplace
skillA skill directoryreplace-if-unmodifiedreplace
toolAn installed, verified command-line toolensurenone
credentialA check that a local credential existsrequire-localnone

Author file, config, and skill resources

canonfig source scan proposes only tool resources. Write every other resource in the profile file. Sync your first two machines builds a complete Claude Code and Codex profile from these shapes.

file

{
  "id": "claude-instructions",
  "kind": "file",
  "target": "~/.claude/CLAUDE.md",
  "spec": { "kind": "file", "source": ".claude/CLAUDE.md" },
  "verify": { "method": "digest" }
}
{
  "id": "review-helper",
  "kind": "file",
  "target": "~/.local/bin/review-helper",
  "spec": { "kind": "file", "content": "#!/bin/sh\ngit diff --stat\n", "executable": true },
  "verify": { "method": "digest" }
}
Spec fieldValue
sourcePath relative to the profile file's directory. See Source paths.
contentInline UTF-8 text. Use either source or content, not both.
encoding"base64" when content is base64. Only with content.
modeDecimal permission bits, for example 384 for 0600.
executabletrue writes mode 0700 when mode is not set.
symlinkToWrite a symlink with this exact target instead of a file. Verify it with symlink.

Without mode, Canonfig writes a regular file with mode 0600, or 0700 when executable is true. Binary files work the same way: point source at them.

config

{
  "id": "codex-config",
  "kind": "config",
  "target": "~/.codex/config.toml",
  "spec": {
    "kind": "config",
    "format": "toml",
    "keys": [
      { "path": "model_reasoning_effort", "value": "high" },
      { "path": "mcp_servers.memory", "value": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-memory"] } }
    ]
  },
  "verify": { "method": "digest" }
}

format is json, toml, or yaml. Each path is a dotted key, and the resource owns the whole value at that key; see Structured configuration ownership. Canonfig writes the target with mode 0600 and creates missing parent directories with mode 0700.

skill and directory

{
  "id": "claude-review-skill",
  "kind": "skill",
  "target": "~/.claude/skills/code-review",
  "spec": {
    "kind": "skill",
    "name": "code-review",
    "files": [
      { "path": "SKILL.md", "source": ".claude/skills/code-review/SKILL.md" },
      { "path": "scripts/check.sh", "source": ".claude/skills/code-review/scripts/check.sh", "executable": true }
    ]
  },
  "verify": { "method": "digest" }
}

A skill needs name; a directory does not. Both list every file in files. Each file has a path inside the tree and the same fields as a file spec. Both also accept:

Spec fieldValue
modeDecimal mode of the root directory.
directoriesDirectories to create even when empty: { "path", "mode" }, both required.
{
  "id": "local-tools",
  "kind": "directory",
  "target": "~/.local/tools",
  "spec": {
    "kind": "directory",
    "mode": 493, // 0755
    "directories": [{ "path": "cache", "mode": 488 }], // 0750
    "files": [{ "path": "current", "symlinkTo": "../bin/rg" }]
  },
  "verify": { "method": "digest" }
}

A symlink target is written exactly as authored, so a relative target stays relative to the link.

tool and credential

A tool spec has toolId, recipes, and optional login and agentInstall. See How Canonfig installs and verifies tools.

agentInstall lets a Configuration Agent install the tool when no recipe fits:

"agentInstall": {
  "paths": ["~/.local/share/canonfig/tools/review-cli"],
  "origins": ["https://registry.npmjs.org"]
}

paths (at least one) are where the agent may write, and origins are exact HTTPS origins it may download from. Without agentInstall, a tool with no usable recipe needs Human Action Required.

A credential spec has one field, reference: the name of a credential each Follower Machine must already hold. The value never leaves the Follower Machine.

Source paths

A source is resolved from the directory that contains the profile file. It must be relative, must not contain .., and must resolve (after following symlinks) inside that directory. A source that is a symlink to a file inside the directory is published as a symlink. For a link that points elsewhere, use symlinkTo. Anything else fails before signing, for example:

resource claude-instructions cannot publish source ../outside.txt: ".." segments are not allowed; keep the source inside the profile file's directory

If the profile file sits in your home directory, sources can point at the real files, and an edit followed by canonfig source publish is the whole update. See Publish a change to your Follower Machines.

Size limit

A revision's metadata (every resource, file entry, and setting, but not file content) must fit in 1 MiB. A directory with many small files can exceed it. canonfig source digest and canonfig source publish refuse an oversized profile with exit 2. Split large resources across separate profiles.

Apply Policies

PolicyBehavior
replaceWrites the desired content over the target. On a directory, also removes entries Canonfig never wrote, so the tree matches exactly.
mirror-ownedAdds and updates desired files, and removes only files Canonfig wrote earlier and nobody changed. Other entries stay.
mergeWrites declared keys and keeps everything else in the file, including the Local Overlay. Removes a key Canonfig owned that the profile dropped, unless a Local Overlay claims it.
replace-if-unmodifiedWrites only when the target still matches what Canonfig last applied, or already matches the profile. A missing target is recreated. A changed target is Follower Drift: kept, reported, and the run stops with exit 4.
append-localKeeps the Source Machine's text in a marked section at the top of the file and the Follower Machine's text below it.
ensureInstalls and verifies a tool. Never removes other software.
require-localChecks that a local credential exists. Otherwise Human Action Required.

Parent directories that desired files need are created even when the profile does not list them. They never count as drift.

Keep local instructions with append-local

Use append-local on a file such as AGENTS.md when each Follower Machine adds its own instructions. The file must use inline content (not source), must not be executable or a symlink, and the content must not contain the markers below or NUL characters. The file on the Follower Machine looks like this:

Follower AGENTS.md
<!-- canonfig:source:start -->
Source instructions
<!-- canonfig:source:end -->

Follower-local instructions
  • Write local instructions below the closing marker. Later syncs replace only the Source Machine's section and keep local text exactly, including whitespace and line endings.
  • On first adoption, the whole existing file becomes local text, even if it matches the Source Machine's text. The Follower Machine then holds the Source Machine's section plus its old copy. Canonfig does not guess which old lines to drop. Back up the file before you publish the policy.
  • Editing or deleting the Source Machine's section is Follower Drift. Move the edit below the closing marker. Repeated or broken markers need manual repair. Deleting the whole file lets Canonfig recreate it.
  • Removing the resource from the profile removes only an unchanged Source Machine section and leaves local text.

To see exactly what first adoption will write, run this on the Follower Machine:

canonfig sync --plan --review-local

It writes a JSON review for each first adoption to an adoption-review-<id> directory under the user data directory's canonfig folder, readable only by you. sourceAddition is the Source Machine's text, retainedLocal is the local text, and proposedText is the complete file apply will write. If either file changes before you apply, review again.

Reviews can contain secrets

A review holds the Follower Machine's local text. Do not paste it into chat or bug reports. Delete the review directory after you read it.

--review-local works only with an interactive --plan; it cannot be combined with --apply, --no-input, or --scheduled.

Verification

Every resource declares verify. Canonfig runs it after every change, including changes made by an agent, and only a passing verification counts.

MethodFieldsAllowed on
digestdigest (optional)directory, config, skill, and regular file
symlinktarget, equal to symlinkTofile with symlinkTo
commandcommand (argument list), expectContains, provesdirectory, config, skill, tool, credential
executable-presentexecutabletool
credential-presentreferencecredential
mcp-qualificationMCP server probestool; see MCP qualification

A regular file uses digest only.

Digests

Write { "method": "digest" } without a value, and publication computes the digest from the content it signs. If you write a digest and it does not match, publication fails before signing:

the profile cannot be published: resource codex-config declares verify.digest <declared>, but its published content has digest <computed>. Set verify.digest to <computed>, or omit it and publish computes it (canonfig source digest prints it)

To print the computed digests without publishing:

canonfig source digest --profile-file ~/canonfig.profile.jsonc
canonfig source digest --profile-file ~/canonfig.profile.jsonc --resource codex-config

source digest exits 0 even when a declared digest differs.

Commands

A command verifier passes when the process exits 0 and, if set, its output contains expectContains. A bare executable name is looked up as described in Verification lookup.

Set "proves": "client-load" only when the command starts the real client and makes it load the applied configuration. canonfig status reports that evidence separately:

{
  "method": "command",
  "command": ["claude", "mcp", "list"],
  "proves": "client-load"
}

Applied Resource Records

After applying a resource, the Follower Machine records exactly what it wrote: the Profile Revision, digest, modes, symlink targets, and files it owns. Canonfig compares the Follower Machine with this record to detect Follower Drift and to decide which files mirror-owned may remove. The record never gives the Follower Machine authority over the profile.

Structured configuration ownership

A config value can be a string, number, boolean, array, or nested object. null is not allowed, because TOML cannot represent it. Arrays keep their order.

Numbers must be finite and, when whole, between -9007199254740991 and 9007199254740991. Canonfig never rounds; a larger number fails publication:

the number at $.resources[0].spec.keys[0].value.big cannot be carried exactly

Write such a value as a string if the consuming tool accepts one.

Each path owns the entire value at that key. A profile cannot declare both mcpServers.memory and mcpServers.memory.command, or the same path twice. A Local Overlay key conflicts with a profile key when one contains the other; the resource then needs Human Action Required until you change the overlay.

Escape a dot inside a key name as \., and a backslash as \\. In JSON each backslash is itself escaped, so the key amp.mcpServers → docs is:

{ "path": "amp\\.mcpServers.docs", "value": { "command": "docs-server" } }

Any other backslash is rejected by publication and by canonfig overlay set.

How Canonfig edits files on the Follower Machine

  • JSON targets may contain // and /* */ comments and trailing commas. Canonfig edits only the owned keys, so comments and layout elsewhere stay.
  • TOML and YAML files are edited in place. Comments, key order, anchors, and value spellings outside owned keys stay. A whole number replacing a float stays a float (2.0).
  • An owned value equal to the profile's value in a different key order is left alone. A changed value is written with sorted keys. Unmanaged keys are never reordered.
  • In YAML, strings such as yes, no, on, off, ~, and 2001-12-14 are written in double quotes so other readers do not convert them.
  • If a change cannot be written without changing values outside the owned keys (for example a key under a shared YAML anchor), the action fails and the file is unchanged.
  • A file on the Follower Machine that does not parse blocks the resources that target it. canonfig sync --plan shows each as human-action with the file, line, and column. Repair the file, then run canonfig sync --apply.

When you narrow ownership from an object to some of its keys, the other keys stay in the file on the Follower Machine. When you widen ownership to the parent, the profile value replaces the whole object. The plan lists kept values under retains.

Compile a profile from code

To check a profile in your own scripts, import the compiler that publication uses:

import { compileProfileJsonc } from "@microck/canonfig/profile-compiler";

const candidate = compileProfileJsonc(profileJsonc);

candidate.digest and candidate.canonicalBytes are what publication would sign. candidate.projections holds the Linux, macOS, and Windows versions of the profile, each with only that platform's tool recipes. Pass { platforms: ["linux"] } to compile fewer platforms. An invalid profile throws before returning anything.

On this page