Skip to main content

Config Deployment

Config deployment pushes AI agent config files (Claude Code, Claude Desktop, Codex, Cursor, and GitHub Copilot) onto developer machines through the Scan Agent. You manage it from Monitor → Policies → Deployments.

Beta

Config deployment is in beta and must be enabled for your organization. Agents other than Claude Code also need the multi-agent policies beta.

Changing anything requires the monitor:manage scope.

How a file reaches a device​

  1. You edit the rules for one file and click Save changes. The whole rule list for that file is saved at once, so a device never sees a half-saved change.
  2. The Scan Agent on each device checks in regularly and reports its device and operating system.
  3. Willow builds the file for that device alone. It finds the device's user and their groups, picks the rules that match, fills in dynamic values for that user, and merges the result. A device only ever receives its own result, never other users' rules or values.
  4. The Scan Agent writes the file to the agent's path for that operating system, either merging into what is there or replacing it.
  5. When a file stops being served, because its rule was removed or disabled or the file or the whole deployment was switched off, the Scan Agent undoes what it applied on its next check.

Resolved files are cached for about a minute, so a save reaches devices on their first check after that.

Files you can deploy​

The Deployments overview shows one box per agent and file, with how many rules each file has and its latest version. Each file has a fixed path per operating system; a file with no path on an operating system is not deployed there.

AgentFiles
Claude CodeUser settings (settings.json), Managed settings (managed-settings.json)
Claude DesktopThird-party inference (config library entry), Managed settings (Linux)
CodexRequirements (requirements.toml), Managed defaults (managed_config.toml)
CursorEnterprise hooks, Sandbox policy, Permissions, CLI config, MDM policy (Linux)
GitHub CopilotManaged settings, VS Code policy (Linux)

Codex files are edited as JSON and written as TOML. Cursor's enterprise hooks.json is not deployed while scanner guard hooks are on, because those already run the guard.

Rules​

Each file holds an ordered list of rules. A rule has:

  • Filters that decide who gets it: Everyone, Groups, Users, and OS. Different filter types must all match, and any one value within a type is enough. For example, groups Engineering and Data plus OS macOS means "in Engineering or Data, and on macOS". A rule needs at least one filter.
  • Content: a saved policy for that agent, or custom JSON. Only custom JSON can use dynamic values.
  • Mode: Merge sets only the rule's keys and leaves the developer's other settings alone. Replace makes the file hold only what is deployed; the original returns when the rule no longer applies.
  • Enabled: a disabled rule is kept but not served, so devices undo what it applied.

When several rules match​

The list order is the priority. Drag rules to reorder them; the top one wins on conflicting keys. All matching rules are merged together, lowest first, so a higher rule's value for a key overrides a lower one's. Some list settings, such as permission lists, are combined instead of overwritten. If any matching rule uses Replace, the merged result replaces the device's file.

A device that is not linked to a user only receives Everyone and OS rules.

Preview a user​

Preview user on a file's page shows the exact file one user's devices receive on a chosen operating system, with dynamic values filled in, and which rules contributed. It shows what is saved, so save your changes first.

Versions and turning things off​

  • Every save that changes a file's rules records a new version. To keep that history in your own repository too, push changes to Git. Revert from the file's menu re-applies an earlier version as a new save, so history is never lost.
  • The switch on a file's page turns that file off. Devices restore what they had before it was deployed. Rules are kept and come back when you turn it on.
  • The switch on the overview turns every file off in the same way.

Dynamic values​

A dynamic value is an organization-wide variable you reference as ${KEY} inside any custom JSON rule. Use it for settings that differ per user, such as an API URL, a region, or a token.

{
"env": {
"ACME_API_URL": "${API_URL}",
"ACME_TOKEN": "${ACME_TOKEN}"
}
}

Manage them from Dynamic values on the overview or on any file's page. In the rule editor, clicking a value inserts its token at the cursor.

How a value is filled in​

When a device's file is built, each ${KEY} becomes:

  1. the device's user's own value, if they have one;
  2. otherwise the variable's default;
  3. otherwise the token is left as written.

A device that is not linked to a user gets the default. A ${...} that does not name one of your variables is left alone, so unrelated settings that use the same syntax are not affected. Write $${KEY} to get a literal ${KEY}.

Keys use letters, digits, and underscores, and start with a letter or underscore. An organization can have up to 200.

Set per-user values with a CSV​

Each value has a CSV button. Upload a file with two columns, email and value:

email,value
jane@acme.com,https://eu.api.acme.com
omar@acme.com,"a,b"
lee@acme.com,
  • The header row is optional, and emails are matched to users in your organization regardless of case.
  • Everything after the first comma is the value, so values can contain commas. Surrounding quotes are removed.
  • An upload only changes the users listed in the file. Everyone else keeps their current value.
  • An empty value, like lee above, removes that user's value so they fall back to the default.
  • Emails that don't belong to a user in your organization are skipped. The confirmation shows how many values were set, cleared, and skipped.
  • The badge on each value shows how many users have their own value.

New values reach devices on their next check. You don't need to save any rules, and no new version is recorded. Deleting a variable deletes all of its per-user values, and rules that reference it keep the literal ${KEY}.

Secrets​

Turn on Secret when you add a value that should not be readable in the dashboard, such as a token.

  • A secret's default and every per-user value are write-only. The dashboard only shows whether a default is set and how many users have a value.
  • Set a secret's default when you add it. To change it later, delete the secret and add it again, then upload its CSV again. You can change per-user values at any time by uploading a new CSV.
  • Devices receive the real value, because the agent has to write it into the file. Preview user also shows it filled in.
warning

Secrets keep values out of the dashboard. They do not hide them from the device or its user. Prefer short-lived or per-user credentials, and rotate them by uploading a new CSV.

Push changes to Git​

Connect a Git repository and Willow commits every change to your deployment rules, so the repository history is your change log. It is push only: Willow writes to the repository and never reads from it.

Set it up​

You need a GitHub or Bitbucket integration first. Then turn pushing on in either place. Both edit the same setting.

  • On the Deployments page, click Git, switch on your provider, choose the repository, and click Save.
  • Under Settings → Git Integration, set Config deployment to Push to GitHub (or Push to Bitbucket) and choose the repository. This option only appears while config deployment is enabled for your organization.

Turn on Commit via pull request to commit to a branch (default webrix-github-sync) and open a pull request, instead of committing to the default branch.

Changing the setting requires the org:edit scope, like the rest of the Git integration.

What gets written​

Each rule has its own folder with two files:

<agent>/<file>/<rule>/meta.json
<agent>/<file>/<rule>/config.json

<agent>/<file> is the id of the config file the rule belongs to, and <rule> is the rule's slug. For example, a rule called Block curl on Claude Code's user settings is written to:

claude_code/settings/block-curl/meta.json
claude_code/settings/block-curl/config.json
File idConfig file
claude_code/settingsClaude Code user settings
claude_code/managed-settingsClaude Code managed settings
claude_desktop/3p-configClaude Desktop third-party inference
claude_desktop/managed-settingsClaude Desktop managed settings
codex/requirementsCodex requirements
codex/managed-configCodex managed defaults
codex/managed-config-userCodex managed defaults (user)
cursor/hooks, cursor/sandbox, cursor/permissions, cursor/cli-config, cursor/mdm-policyCursor files
github_copilot/managed-settings, github_copilot/vscode-policyGitHub Copilot files

meta.json describes the rule:

{
"slug": "block-curl",
"name": "Block curl",
"file": "claude_code/settings",
"agent": "claude_code",
"priority": 2,
"mode": "merge",
"enabled": true,
"source": { "type": "json" },
"filters": [
{ "type": "group", "id": "5f0c…", "name": "Engineering" }
]
}
  • source is { "type": "json" } for custom JSON, or { "type": "policy", "policy_id": "…", "policy_name": "…" } for a rule built on a policy.
  • filters lists who the rule targets: { "type": "all" }, groups with their name, and users with their name and email.
  • priority is the rule's place in the list. A higher number wins on conflicting keys.

config.json is the content the rule deploys. For a policy rule it is the policy rendered into the file's format. Dynamic values are written as the ${KEY} token, never filled in, so no per-user value or secret ever reaches the repository.

Timestamps are left out on purpose, so a commit only shows real changes.

Rule slugs​

A rule's slug names its folder, so it stays the same when you edit the rule.

  • A new rule gets a slug from its name: Block curl becomes block-curl. A rule without a name gets rule.
  • If the slug is taken by another rule on the same file, a number is added: block-curl-2.
  • Renaming a rule does not change its slug, so its history stays in one folder.
  • Rules that existed before Git push was added were given slugs from their names.
  • Reverting to an earlier version restores that version's slugs.

When Willow pushes​

Willow pushes a file's rules each time:

  • you click Save changes on the file;
  • you revert to an earlier version;
  • you edit or delete a policy that one of its rules uses.

Each push writes all of that file's rules in one commit, with a message such as Update claude_code/settings (v7). A revert adds the version, for example Update claude_code/settings (v8): Reverted to v3. Nothing is committed when the content is already in the repository.

A rule you delete has its folder removed in the same push. A folder under the file that does not match any rule is removed too, so don't keep your own files in a rule folder. Files that are not meta.json or config.json are left alone.

If a push fails, for example because the repository is unreachable or the integration is paused, your save is not affected. The failure is recorded in Git Sync Logs, which the Git dialog links to. The next push, or Push all rules now in the Git dialog, brings the repository back in line with Willow.

What is not pushed

Some changes do not go through a rule save, so they are not committed until the next push: renaming a group or user, deleting a group or user, turning a file or the whole deployment on or off, and changing dynamic values. Group and user names in meta.json are as of the last push.

Edits in the repository are overwritten

Willow is the source of truth. Editing meta.json or config.json in the repository does not change what devices receive, and the next push replaces your edit.

Check what landed on a device​

The Scan Agent uploads each AI tool's settings files on every scan, so you can confirm what a deployment wrote. There is no setting to turn this on. On macOS this includes Claude Desktop's third-party inference configs under ~/Library/Application Support/Claude-3p/configLibrary/. As with every settings file, API keys, tokens, credentials, and headers are replaced with hashes before they leave the device.