APM

>Agent Skill

@microsoft/settings-precedence

skilldevelopment

VS Code settings precedence rules and common pitfalls. Essential for any code that reads or writes settings. Covers getConfiguration scope, inspect() vs get(), and multi-workspace handling.

pythontypescript
apm::install
$apm install @microsoft/settings-precedence
apm::skill.md
---
name: settings-precedence
description: VS Code settings precedence rules and common pitfalls. Essential for any code that reads or writes settings. Covers getConfiguration scope, inspect() vs get(), and multi-workspace handling.
argument-hint: Review settings handling in [file or component]
user-invocable: false
---

# VS Code Settings Precedence

Settings precedence bugs corrupt user configurations. This skill documents the correct patterns.

## Precedence Order (Highest to Lowest)

1. **Workspace folder value** - Per-folder in multi-root workspace
2. **Workspace value** - `.vscode/settings.json` or `.code-workspace`
3. **User/global value** - User `settings.json`
4. **Default value** - From extension's `package.json` (⚠️ may come from other extensions!)

## Core Rules

### Rule 1: Always Pass Scope to getConfiguration()

```typescript
// ❌ WRONG: Missing scope
const config = vscode.workspace.getConfiguration('python-envs');
const value = config.get('pythonProjects');
// workspaceFolderValue will be UNDEFINED because VS Code doesn't know which folder!

// ✅ RIGHT: Pass scope (workspace folder or document URI)
const config = vscode.workspace.getConfiguration('python-envs', workspaceFolder);
const value = config.get('pythonProjects');
```

**When to pass scope:**

- Reading per-resource settings (`scope: "resource"` in package.json)
- Any multi-workspace scenario
- When you need `workspaceFolderValue` from inspect()

### Rule 2: Use inspect() to Check Explicit Values

```typescript
// ❌ WRONG: get() returns defaultValue even from other extensions!
const config = vscode.workspace.getConfiguration('python');
if (config.get('useEnvironmentsExtension')) {
    // May return true from another extension's package.json default!
}

// ✅ RIGHT: Use inspect() and check explicit values only
const config = vscode.workspace.getConfiguration('python', scope);
const inspected = config.inspect('useEnvironmentsExtension');

const hasExplicitValue =
    inspected?.globalValue !== undefined ||
    inspected?.workspaceValue !== undefined ||
    inspected?.workspaceFolderValue !== undefined;

if (hasExplicitValue) {
    // User explicitly set this value
    const effectiveValue = inspected?.workspaceFolderValue ?? inspected?.workspaceValue ?? inspected?.globalValue;
}
```

### Rule 3: Don't Overwrite User's Explicit Values

```typescript
// ❌ WRONG: Unconditionally writing to settings
await config.update('pythonPath', detectedPath, ConfigurationTarget.Workspace);
// Overwrites user's explicit choice!

// ✅ RIGHT: Check for existing explicit values first
const inspected = config.inspect('pythonPath');
const hasUserValue = inspected?.workspaceValue !== undefined;

if (!hasUserValue) {
    // Only set if user hasn't explicitly chosen
    await config.update('pythonPath', detectedPath, ConfigurationTarget.Workspace);
}
```

### Rule 4: Update at the Correct Scope

```typescript
// Configuration targets (least to most specific)
ConfigurationTarget.Global; // User settings.json
ConfigurationTarget.Workspace; // .vscode/settings.json or .code-workspace
ConfigurationTarget.WorkspaceFolder; // Per-folder in multi-root

// To remove a setting, update with undefined
await config.update('pythonPath', undefined, ConfigurationTarget.Workspace);
```

## Multi-Root Workspace Handling

### The `workspace` Property

For multi-root workspaces, `pythonProjects` settings need a `workspace` property:

```json
{
    "python-envs.pythonProjects": [
        {
            "path": ".",
            "workspace": "/path/to/workspace-folder",
            "envManager": "ms-python.python:venv"
        }
    ]
}
```

Without the `workspace` property, settings get mixed up between folders.

### Getting the Right Workspace Folder

```typescript
// ❌ WRONG: Always using first workspace folder
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];

// ✅ RIGHT: Get folder for specific document/file
const workspaceFolder = vscode.workspace.getWorkspaceFolder(documentUri) ?? vscode.workspace.workspaceFolders?.[0];

// When you have a path but not a URI
const uri = vscode.Uri.file(filePath);
const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
```

## Common Issues

### Issue: workspaceFolderValue is undefined

**Cause:** Missing scope parameter in `getConfiguration()`

```typescript
// This returns undefined for workspaceFolderValue!
const config = vscode.workspace.getConfiguration('python-envs');
const inspected = config.inspect('pythonProjects');
console.log(inspected?.workspaceFolderValue); // undefined!

// Fix: Pass scope
const config = vscode.workspace.getConfiguration('python-envs', workspaceFolder.uri);
const inspected = config.inspect('pythonProjects');
console.log(inspected?.workspaceFolderValue); // Now works!
```

### Issue: defaultValue from other extensions

**Cause:** Using `get()` instead of `inspect()` for boolean checks

The `defaultValue` in `inspect()` may come from ANY extension's `package.json`, not just yours:

```typescript
// Another extension might have in their package.json:
// "python.useEnvironmentsExtension": { "default": true }

// Your check will be wrong:
config.get('python.useEnvironmentsExtension') // true from other extension!

// Fix: Only check explicit values
const inspected = config.inspect('python.useEnvironmentsExtension');
if (inspected?.globalValue === true || ...) { }
```

### Issue: Settings overwritten on reload

**Cause:** Not checking for existing values before writing

```typescript
// During extension activation, this overwrites user's config!
await config.update('defaultEnvManager', 'venv', ConfigurationTarget.Global);

// Fix: Only write defaults if no value exists
const current = config.inspect('defaultEnvManager');
if (current?.globalValue === undefined && current?.workspaceValue === undefined) {
    await config.update('defaultEnvManager', 'venv', ConfigurationTarget.Global);
}
```

### Issue: Settings mixed up in multi-root

**Cause:** Not including workspace identifier in settings

```typescript
// Without workspace identifier, can't tell which folder this belongs to
{
    "python-envs.pythonProjects": [
        { "path": ".", "envManager": "venv" }  // Which workspace?
    ]
}

// Fix: Always include workspace when saving
const project = {
    path: projectPath,
    workspace: workspaceFolder.uri.fsPath,
    envManager: selectedManager
};
```

## Complete Example: Safe Settings Read/Write

```typescript
import * as vscode from 'vscode';

async function getProjectConfig(projectUri: vscode.Uri): Promise<ProjectConfig | undefined> {
    const workspaceFolder = vscode.workspace.getWorkspaceFolder(projectUri);
    if (!workspaceFolder) {
        return undefined;
    }

    // Always pass scope!
    const config = vscode.workspace.getConfiguration('python-envs', workspaceFolder.uri);

    // Use inspect() to understand where values come from
    const inspected = config.inspect<ProjectConfig[]>('pythonProjects');

    // Prefer most specific value
    const projects = inspected?.workspaceFolderValue ?? inspected?.workspaceValue ?? inspected?.globalValue ?? []; // Don't use defaultValue!

    // Find project matching the URI
    return projects.find((p) => path.resolve(workspaceFolder.uri.fsPath, p.path) === projectUri.fsPath);
}

async function saveProjectConfig(projectUri: vscode.Uri, projectConfig: ProjectConfig): Promise<void> {
    const workspaceFolder = vscode.workspace.getWorkspaceFolder(projectUri);
    if (!workspaceFolder) {
        return;
    }

    const config = vscode.workspace.getConfiguration('python-envs', workspaceFolder.uri);

    const inspected = config.inspect<ProjectConfig[]>('pythonProjects');

    // Get existing projects (not including defaults!)
    const existingProjects = inspected?.workspaceFolderValue ?? inspected?.workspaceValue ?? [];

    // Ensure workspace property for multi-root
    const configToSave: ProjectConfig = {
        ...projectConfig,
        workspace: workspaceFolder.uri.fsPath,
    };

    // Update or add
    const projectIndex = existingProjects.findIndex(
        (p) => path.resolve(workspaceFolder.uri.fsPath, p.path) === projectUri.fsPath,
    );

    const updatedProjects = [...existingProjects];
    if (projectIndex >= 0) {
        updatedProjects[projectIndex] = configToSave;
    } else {
        updatedProjects.push(configToSave);
    }

    // Write to workspace folder scope in multi-root
    const target =
        vscode.workspace.workspaceFolders?.length > 1
            ? vscode.ConfigurationTarget.WorkspaceFolder
            : vscode.ConfigurationTarget.Workspace;

    await config.update('pythonProjects', updatedProjects, target);
}
```