Welcome back! In Plugin Identification & Discovery, we learned how the system figures out who a plugin is (identifying my-plugin as my-plugin@marketplace).
Now we face a new problem: Disagreement.
What if you want a plugin enabled globally, but disabled for one specific project? Or what if your team mandates a "linter" plugin for the project, but you want to turn it off temporarily on your machine to debug something?
This chapter explains the Scope Resolution Strategyβthe rules the system uses to decide which configuration wins when there are conflicting settings.
Imagine an open-plan office.
The Central Use Case:
You are working on a team project. The team has a strict-linter plugin enabled in the shared project settings (.claude/settings.json). This file is committed to Git, so everyone has it.
You want to disable it only on your computer without breaking it for your teammates.
You run:
tengu plugin disable strict-linter --scope local
The system needs a strategy to ensure your "local" preference takes priority over the "project" rule.
We organize configuration into three layers, called Scopes.
~/.config/claude/config.json.claude/settings.json (inside your project folder).claude/claude.json (inside your project folder)
Just like in CSS, where an ID selector (#header) overrides a class selector (.header), our scopes have a strict precedence order.
The Golden Rule: Local > Project > User.
Local says "Disabled", the plugin is disabled (even if Project says "Enabled").Local says nothing, we check Project.
When you run the disable command with --scope local, the system writes enabled: false into your private local settings file.
When the system later asks, "Is the strict-linter enabled?", it runs a resolution check:
enabled: false. STOP. Result: Disabled.If you hadn't set a local override:
enabled: true. STOP. Result: Enabled.Let's look at how this logic is implemented in the code.
When the application starts or a command runs, it doesn't just read one file. It hunts for the plugin configuration.
In pluginOperations.ts, the function findPluginInSettings implements this hierarchy.
We define the search order explicitly:
// From pluginOperations.ts
function findPluginInSettings(plugin: string) {
// 1. Define the priority: Local first, then Project, then User
const searchOrder: InstallableScope[] = ['local', 'project', 'user']
// 2. Loop through them in order
for (const scope of searchOrder) {
// ... logic to check the file ...
Explanation:
We create an array ['local', 'project', 'user']. This simple array defines the entire philosophy of our specificity. If we wanted user settings to override everything, we would just change the order of this array.
Inside the loop, we check if the plugin exists in that specific scope.
// Inside the loop...
const settings = getSettingsForSource(scopeToSettingSource(scope))
// If the file exists and has plugins enabled...
if (settings && settings.enabledPlugins) {
// Check if our plugin is mentioned here
if (checkIfPluginIsHere(settings.enabledPlugins, plugin)) {
// Found it! Return immediately. The loop stops.
return { pluginId: key, scope }
}
}
} // End loop
return null // Not found anywhere
}
Explanation:
scopeToSettingSource(scope): This helper translates the string 'user' into the actual file handler for userSettings.return statement is crucial. As soon as we find a match in a high-priority scope (like local), we exit the function. This prevents lower-priority scopes from interfering.When writing settings (e.g., enabling a plugin), the system is smart. It knows about the hierarchy.
If you try to disable a plugin that is enabled in the Project scope, the system warns you if you are targeting the wrong scope.
// From setPluginEnabledOp in pluginOperations.ts
// Imagine we found the plugin enabled in 'project' scope
const foundScope = 'project'
// But the user requested to change it in 'user' scope (which is lower priority)
const requestedScope = 'user'
// We check precedence (Local=2, Project=1, User=0)
const isOverride = precedence[requestedScope] > precedence[foundScope]
// If you aren't overriding, and you are editing the wrong scope, we warn you.
if (!isOverride && foundScope !== requestedScope) {
return { success: false, message: "Plugin is managed in Project scope..." }
}
Explanation: This logic prevents confusion.
isOverride becomes true. The system allows this because Local is allowed to override Project.The Scope Resolution Strategy ensures that your plugin system is flexible enough for team collaboration but personal enough for individual preferences.
.claude/claude.json) is for you..claude/settings.json) is for the team.Config) is for defaults.Now that we know which plugin we want (Chapter 2) and which settings apply (Chapter 3), we are ready to perform the actual work: installing files, moving data, and validating versions.
Next, we dive into the engine room: Core Plugin Operations
Generated by Code IQ