How Project-Level .qwen/settings.json Overrides User-Level Configuration in Qwen Code
Project-level .qwen/settings.json overrides user-level settings through a deep-merge algorithm that gives workspace-specific keys precedence while inheriting undefined values from the global configuration.
As implemented in QwenLM/qwen-code, the CLI employs a hierarchical configuration system that loads settings from two distinct scopes. Understanding how project-level .qwen/settings.json overrides user-level configuration allows developers to manage sensitive credentials and environment-specific behaviors without duplicating entire configuration files.
Configuration File Locations
Qwen Code resolves settings from two filesystem locations, distinguished by scope and accessed through specific Storage class methods:
| Scope | File Location | Resolution Method |
|---|---|---|
| User (global) | ~/.qwen/settings.json |
Storage.getGlobalSettingsPath() returns <home-dir>/.qwen/settings.json |
| Project (workspace) | <project-root>/.qwen/settings.json |
Storage.getWorkspaceSettingsPath() returns <cwd>/.qwen/settings.json |
According to the source code in packages/core/src/config/storage.ts, the workspace path is defined on lines 18-20, while the global path retrieval operates on lines 40-42. These methods provide the canonical paths used throughout the configuration loading lifecycle.
The Configuration Loading Process
The loadSettings() function in packages/cli/src/config/settings.ts (approximately lines 900-960) orchestrates the merge operation. This function implements a deterministic loading strategy that ensures project-level values always take precedence.
Step-by-Step Merge Logic
The loader executes three distinct phases:
- Load global settings first – The user-level file at
~/.qwen/settings.jsonis parsed into a base configuration object. - Load workspace settings if present – The project-level file at
./.qwen/settings.jsonis read only if it exists on disk. - Deep-merge with precedence – The workspace configuration is merged onto the global configuration using a deep-merge utility (similar to
lodash.merge), where project-level keys replace identical keys from the user-level file.
The resulting configuration follows the pattern finalSettings = { ...global, ...workspace } with nested objects recursively merged rather than shallow-replaced.
Practical Code Examples
Manual Configuration Loading
You can replicate the merge behavior manually using the core storage utilities:
import { readFileSync } from 'node:fs';
import { Storage } from '@/core/config/storage';
import merge from 'lodash.merge';
// Load user-level settings
let userSettings = {};
try {
userSettings = JSON.parse(
readFileSync(Storage.getGlobalSettingsPath(), 'utf8')
);
} catch { /* ignore if file does not exist */ }
// Load project-level settings (if present)
let workspaceSettings = {};
try {
workspaceSettings = JSON.parse(
readFileSync(Storage.getWorkspaceSettingsPath(), 'utf8')
);
} catch { /* ignore if file does not exist */ }
// Deep-merge – workspace overrides user
const finalSettings = merge({}, userSettings, workspaceSettings);
Using the Built-in Loader
For production use, import the CLI's optimized loader:
import { loadSettings } from '@/cli/config/settings';
const cwd = process.cwd(); // e.g., /my/project
const settings = loadSettings(cwd); // returns the merged config
console.log(settings);
Override Example with API Keys
Consider this configuration scenario demonstrating how project-level .qwen/settings.json overrides user-level configuration:
// ~/.qwen/settings.json
{
"modelProviders": {
"openai": { "apiKey": "USER_KEY" }
},
"telemetry": { "enabled": false }
}
// ./.qwen/settings.json (project-specific)
{
"modelProviders": {
"openai": { "apiKey": "PROJECT_KEY" }
},
"telemetry": { "enabled": true }
}
After the merge operation, settings.modelProviders.openai.apiKey resolves to "PROJECT_KEY" and settings.telemetry.enabled evaluates to true. The project-level values completely replace their global counterparts while preserving any user-level settings not explicitly overridden.
Key Source Files
Understanding the override mechanism requires familiarity with these specific source locations:
| File | Role |
|---|---|
packages/core/src/config/storage.ts |
Defines getGlobalSettingsPath() and getWorkspaceSettingsPath() for filesystem resolution |
packages/cli/src/config/settings.ts |
Implements loadSettings() with the deep-merge logic that enforces project-level precedence |
docs/users/configuration/settings.md |
Documents the two-level configuration model and override behavior for end users |
Summary
- Project-level
.qwen/settings.jsontakes precedence over~/.qwen/settings.jsonthrough a deep-merge operation performed byloadSettings()in the CLI package. - The merge is non-destructive – undefined project-level values inherit from the global configuration, while defined values completely replace global counterparts at any nesting level.
- Path resolution is handled by
Storage.getGlobalSettingsPath()andStorage.getWorkspaceSettingsPath()inpackages/core/src/config/storage.ts. - Nested objects are recursively merged rather than shallow-copied, allowing granular overrides of specific provider settings without redefining entire configuration blocks.
Frequently Asked Questions
What happens if the project-level settings.json is missing?
If <project-root>/.qwen/settings.json does not exist, the CLI uses only the user-level configuration from ~/.qwen/settings.json. The loadSettings() function wraps the workspace file read in a try-catch block, ensuring the absence of a project file does not prevent startup.
Does the override work for deeply nested configuration objects?
Yes. The merge algorithm uses deep-merge logic (similar to lodash.merge) that recursively combines nested objects. This means you can override a single property like modelProviders.openai.apiKey without losing other provider settings defined in the global configuration.
Can I prevent specific global settings from being inherited?
No. The current implementation does not support explicit nullification or deletion of global keys through the project-level file. To effectively disable a global setting, you must explicitly set it to a neutral value (such as an empty string or false) in the project-level configuration rather than omitting it.
Where should I store sensitive API keys?
Store sensitive API keys in the project-level .qwen/settings.json file and add that file to your .gitignore. This prevents credentials from being committed to version control while allowing different team members to maintain their own global defaults in ~/.qwen/settings.json.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →