microsoft/add-policy
Use when adding, modifying, or reviewing VS Code configuration policies. Covers the full policy lifecycle from registration to export to platform-specific artifacts. Run on ANY change that adds a `policy:` field to a configuration property.
npx skills add https://github.com/microsoft/vscode --skill add-policy
Policies allow enterprise administrators to lock configuration settings via OS-level mechanisms (Windows Group Policy, macOS managed preferences, Linux config files) or via Copilot account-level policy data. This skill covers the complete procedure.
policy: field to any configuration propertyIPolicyDatapolicyReference| Source | Implementation | How it reads policies |
|--------|---------------|----------------------|
| OS-level (Windows registry, macOS plist) | NativePolicyService via @vscode/policy-watcher | Watches Software\Policies\Microsoft\{productName} (Windows) or bundle identifier prefs (macOS) |
| Linux file | FilePolicyService | Reads /etc/vscode/policy.json |
| Account/GitHub | AccountPolicyService | Reads IPolicyData from IDefaultAccountService.policyData, applies value() function. Server-delivered managed settings arrive on policyData.managedSettings; native MDM (INativeManagedSettingsService) and a file on disk (IFileManagedSettingsService) are separate inputs that AccountPolicyService merges in getPolicyData() via pickManagedSettings(nativeMdm, server, file) (per-key precedence native MDM > server > file; a key locked by a higher channel cannot be overwritten, keys it leaves unset fall through to lower channels) |
| Copilot managed settings (native MDM) | NativeManagedSettingsService via @vscode/policy-watcher | Watches SOFTWARE\Policies\GitHubCopilot (Windows) / com.github.copilot prefs (macOS); feeds the canonical managedSettings bag — see github-managed-settings.md |
| Copilot managed settings (file) | FileManagedSettingsService | Reads + watches managed-settings.json from a well-known per-OS path in the main process, exposed to renderers over IPC; lowest-precedence managed-settings channel — see github-managed-settings.md |
| Multiplex | MultiplexPolicyService | In the main process, combines multiple OS/file policy readers; in desktop and Agents-window renderers, combines the main-process PolicyChannelClient with AccountPolicyService |
| File | Purpose |
|------|---------|
| src/vs/base/common/policy.ts | PolicyCategory enum, IPolicy interface, IPolicyReference, ManagedSettingsData, IManagedSettingsPolicyDefinitions |
| src/vs/platform/policy/common/policy.ts | IPolicyService, AbstractPolicyService, PolicyDefinition, toSerializablePolicyDefinition (drops the non-cloneable value() for IPC), getRestrictedPolicyValue |
| src/vs/platform/policy/common/copilotManagedSettings.ts | Managed-settings key constants + well-known file paths, collectManagedSettingsDefinitions, projectManagedSettings, the shared normalizeManagedSettings (single normalizer for all channels), pickManagedSettings (per-key channel precedence), INativeManagedSettingsService / IFileManagedSettingsService |
| src/vs/platform/policy/node/nativeManagedSettingsService.ts | Native MDM watcher (@vscode/policy-watcher) for Copilot managed settings |
| src/vs/platform/policy/common/fileManagedSettingsService.ts | File-based channel: reads + watches managed-settings.json on a well-known per-OS path, normalizes via normalizeManagedSettings |
| src/vs/platform/configuration/common/configurations.ts | PolicyConfiguration — bridges policies to configuration values; parses JSON-string managed settings back to typed values; applies values to policyReference settings |
| src/vs/platform/configuration/common/configurationRegistry.ts | policy / policyReference registration; getPolicyReferenceConfigurations() (name → subordinate settings) |
| src/vs/workbench/services/policies/common/accountPolicyService.ts | Account/GitHub-based policy evaluation; selects + projects managed settings (native MDM over server; single authoritative layer) |
| src/vs/workbench/services/accounts/browser/managedSettings.ts | adaptManagedSettings — normalizes the server managed_settings response into the canonical bag |
| src/vs/workbench/services/policies/common/multiplexPolicyService.ts | Combines multiple policy services |
| src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts | --export-policy-data CLI handler |
| src/vs/base/common/defaultAccount.ts | IPolicyData interface (incl. managedSettings) for account-level policy fields |
| build/lib/policies/policyData.jsonc | Auto-generated policy catalog incl. referencedSettings (DO NOT edit manually) |
| build/lib/policies/policyGenerator.ts | Generates ADMX/ADML (Windows), plist (macOS), JSON (Linux) |
| build/lib/test/policyConversion.test.ts | Tests for policy artifact generation |
policy field to the configuration propertyFind the configuration registration (typically in a *.contribution.ts file) and add a policy object to the property schema.
Required fields:
Determining minimumVersion: Always read version from the root package.json and use the major.minor portion. For example, if package.json has "version": "1.112.0", use minimumVersion: '1.112'. Never hardcode an old version like '1.99'.
policy: {
name: 'MyPolicyName', // PascalCase, unique across all policies
category: PolicyCategory.InteractiveSession, // From PolicyCategory enum
minimumVersion: '1.112', // Use major.minor from package.json version
localization: {
description: {
key: 'my.config.key', // NLS key for the description
value: nls.localize('my.config.key', "Human-readable description."),
}
}
}
Optional: value function for account-based policy:
If this policy should also be controllable via Copilot account policy data (from IPolicyData), add a value function:
policy: {
name: 'MyPolicyName',
category: PolicyCategory.InteractiveSession,
minimumVersion: '1.112', // Use major.minor from package.json version
value: (policyData) => policyData.my_field === false ? false : undefined,
localization: { /* ... */ }
}
The value function receives IPolicyData (from src/vs/base/common/defaultAccount.ts) and should:
undefined to not apply any account-level override (falls through to OS policy or user setting)If you need a new field on IPolicyData, add it to the interface in src/vs/base/common/defaultAccount.ts.
Optional: enumDescriptions for enum/string policies:
IMPORTANT: If the configuration property has type: 'string' and an enum array, you must include enumDescriptions in the localization block with the same number of entries as the enum array. Without this, npm run export-policy-data will fail with: enumDescriptions must exist and have the same length as enum for policy "...".
localization: {
description: { key: '...', value: nls.localize('...', "...") },
enumDescriptions: [
{ key: 'opt.none', value: nls.localize('opt.none', "No access.") },
{ key: 'opt.all', value: nls.localize('opt.all', "Full access.") },
]
}
PolicyCategory is importedimport { PolicyCategory } from '../../../../base/common/policy.js';
Existing categories in the PolicyCategory enum:
ExtensionsIntegratedTerminalInteractiveSession (used for all chat/Copilot policies)TelemetryUpdateIf you need a new category, add it to PolicyCategory in src/vs/base/common/policy.ts and add corresponding PolicyCategoryData localization.
Check the VS Code - Build watch task output, or run:
npm run typecheck-client
Regenerate the auto-generated policy catalog:
npm run export-policy-data
This script handles transpilation, sets up GITHUB_TOKEN (via gh CLI or GitHub OAuth device flow), and runs --export-policy-data. The export command reads extension configuration policies from the distro's product.json via the GitHub API and merges them into the output.
The command launches both the regular Workbench and the Agents window with isolated, empty
profiles, then merges their policy catalogs into one deterministic union. It fails if both
entrypoints register conflicting metadata for the same policy. This ensures policies and
policyReference settings from either product surface are included.
This updates build/lib/policies/policyData.jsonc. **Never edit or synthesize this file
manually, including with an LLM. Do not invoke --export-policy-data directly.** The npm
command is the only supported generator because a single product entrypoint produces an
incomplete catalog. Verify your policy appears in the output and include the generated file
in the same change. You will need code review from a codeowner to merge the change to main.
Extension authors cannot add policy: fields directly—their settings are defined in the extension's package.json, not in VS Code core. Instead, policies for extension settings are defined in vscode-distro's product.json under the extensionConfigurationPolicy key.
extensionConfigurationPolicy map lives in vscode-distro under mixin/{quality}/product.json (stable, insider, exploration).product.json, configurationExtensionPoint.ts reads extensionConfigurationPolicy and attaches matching policy objects to extension-contributed configuration properties.--export-policy-data command fetches the distro's product.json at the commit pinned in package.json and merges extension policies into the output. Use npm run export-policy-data which sets up authentication automatically.Each entry in extensionConfigurationPolicy must include:
"extensionConfigurationPolicy": {
"publisher.extension.settingName": {
"name": "PolicyName",
"category": "InteractiveSession",
"minimumVersion": "1.99",
"description": "Human-readable description."
}
}
name: PascalCase policy name, unique across all policiescategory: Must be a valid PolicyCategory enum value (e.g., InteractiveSession, Extensions)minimumVersion: The VS Code version that first shipped this policydescription: Human-readable description string used to generate localization key/value pairs for ADMX/ADML/macOS/Linux policy artifactsextensionConfigurationPolicy in all three quality product.json files in vscode-distro (mixin/stable/, mixin/insider/, mixin/exploration/)distro commit hash in package.json to point to the distro commit that includes your new entry — the export command fetches extension policies from the pinned distro commitpolicyData.jsonc by running npm run export-policy-data (see Step 4 above)src/vs/workbench/contrib/policyExport/test/node/extensionPolicyFixture.json with the new entryThe file src/vs/workbench/contrib/policyExport/test/node/extensionPolicyFixture.json is a test fixture that must stay in sync with the extension policies in the checked-in policyData.jsonc. When extension policies are added or changed in the distro, this fixture must be updated to match — otherwise the integration test will fail because the test output (generated from the fixture) won't match the checked-in file (generated from the real distro).
| Consumer | What it reads | Output |
|----------|--------------|--------|
| policyGenerator.ts | policyData.jsonc | ADMX/ADML (Windows GP), .mobileconfig (macOS), policy.json (Linux) |
| vscode-website (gulpfile.policies.js) | policyData.jsonc | Enterprise policy reference table at code.visualstudio.com/docs/enterprise/policies |
| vscode-docs | Generated from website build | docs/enterprise/policies.md |
If your setting is a GitHub Preview Feature — meaning it's a Copilot/chat feature that organizations can disable via their GitHub account-level policy — you must add a value function that checks policyData.chat_preview_features_enabled.
Add the chat_preview_features_enabled check when all of these apply:
'preview' or 'experimental')The chat_preview_features_enabled field on IPolicyData (defined in src/vs/base/common/defaultAccount.ts) is populated from the user's GitHub Copilot token entitlements. When an organization admin disables preview features, chat_preview_features_enabled is set to false.
Add a value function to the policy that returns a disabling value when chat_preview_features_enabled === false, and undefined otherwise (to fall through to the user's own setting):
policy: {
name: 'MyPreviewFeaturePolicy',
category: PolicyCategory.InteractiveSession,
minimumVersion: '1.xx', // Must match the first VS Code release that ships this policy.
value: (policyData) => policyData.chat_preview_features_enabled === false ? false : undefined,
localization: {
description: {
key: 'my.setting.description',
value: nls.localize('my.setting.description', "Description of the setting."),
}
}
}
Key details:
=== false, not !policyData.chat_preview_features_enabled — the field is optional and undefined means "no policy data available", which should not disable the feature.undefined when the flag is not false so the account-level policy does not override the user's setting.false for booleans, a restrictive string/enum value for other types.See chat.tools.global.autoApprove and chat.useHooks in src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts for existing settings that use this pattern.
GitHub Copilot enterprise admins can lock settings through a managed-settings bag.
VS Code feeds the bag from two channels: native MDM (Windows registry / macOS plist)
and the GitHub /copilot_internal/managed_settings endpoint. (The external
managed-settings-schema.json also describes a managed-settings.json file channel, but
VS Code does not read such a file.) Both VS Code channels converge on
IPolicyData.managedSettings (a flat dot-path bag) and are consumed by the existing
policy.value(policyData) callback — there is no new IPolicyService.
To drive a policy from a managed setting, declare managedSettings on the policy and
read policyData.managedSettings?.[KEY] in value (the real ChatToolsAutoApprove also
ORs in chat_preview_features_enabled === false):
// Existing policy shown verbatim; `minimumVersion: '1.99'` is its historical value —
// a NEW policy derives minimumVersion from package.json major.minor (see Step 1).
policy: {
name: 'ChatToolsAutoApprove',
category: PolicyCategory.InteractiveSession,
minimumVersion: '1.99',
value: (policyData) =>
policyData.managedSettings?.[COPILOT_DISABLE_BYPASS_PERMISSIONS_MODE_KEY] === 'disable'
|| policyData.chat_preview_features_enabled === false ? false : undefined,
managedSettings: {
[COPILOT_DISABLE_BYPASS_PERMISSIONS_MODE_KEY]: { type: 'string' },
},
localization: { /* ... */ }
}
**This is its own modality — full details, schema source of truth, helpers, wiring, and
the new-key checklist are in github-managed-settings.md.**
Read it before adding or reviewing any managed-settings key.
Testing locally: to exercise the account/managed-settings flow without the real
GitHub backend, use the mock policy server — see
local-testing.md.
policyReference)A single policy can govern multiple settings (e.g. gate an agent in both the editor
window and the Agents window). The owner declares the full policy: { name, … };
other settings declare policyReference: { name } pointing at the owner's policy name.
// Owner setting (existing policy; `minimumVersion: '1.126'` is its historical value —
// a NEW policy uses package.json major.minor, see Step 1)
policy: { name: 'Codex3PIntegration', category: PolicyCategory.InteractiveSession, minimumVersion: '1.126', /* ... */ }
// Subordinate setting (no type/value/localization of its own)
policyReference: { name: 'Codex3PIntegration' }
policyReference is not managed-settings-specific: use it whenever one enterprise
policy should lock multiple settings to the same value. The reference is a **pure
pointer**. It contributes no type, value, managedSettings, restrictedValue, or
localization of its own; the owner remains the single source of truth for policy
metadata and runtime behavior.
Key rules and internals:
policy and policyReference (rejected duringconfiguration registration).
policy; additional settings attachwith policyReference.
npm run export-policy-dataenforces this because the same resolved policy value is applied verbatim to owner and
references.
ConfigurationRegistry.getPolicyReferenceConfigurations() tracks `policyName →Set<settingKey>, and PolicyConfiguration` updates both the owner setting and all
registered references when the policy value changes.
AbstractPolicyService.serialize() uses toSerializablePolicyDefinition() to stripthe non-cloneable value() callback before sending policy definitions over IPC.
AbstractPolicyService.updatePolicyDefinitions() replaces definitions per policyname, so a late-registering owner supersedes an earlier reference fallback; if the
owner is removed, a reference can still provide a bare type fallback.
referencedSettings for references that are registeredduring export, and Developer: Policy Diagnostics lists registered owner/reference
settings under the same policy name.
For managed-settings-specific examples that combine policyReference with Copilot
managed settings, see github-managed-settings.md.
Search the codebase for policy: to find all the examples of different policy configurations.
build/lib/policies/policyData.jsonc (its header explicitly forbids it). If npm run export-policy-data fails, fix the command or its source policies instead of patching the JSON. Always run it from the worktree containing the policy source change.scripts/code.{sh,bat} --export-policy-data invocation exports only the selected product entrypoint and is necessarily incomplete. Always use npm run export-policy-data, which isolates profiles, exports both the Workbench and Agents window, and merges their catalogs. The blocking PolicyExport integration test runs this same canonical path in check mode.getPolicyData() merge body) in the skill rots the moment the source changes and adds no information beyond the source itself. State the contract in prose (e.g. "native MDM managed settings win over the server-delivered channel; the two layers are never merged") and point to the source for the implementation. Reserve code blocks for the author-facing API contract a contributor must follow — how to *declare* a policy / managedSettings / value callback — not for restating runtime plumbing.Take microsoft/add-policy from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.