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:

  1. Load global settings first – The user-level file at ~/.qwen/settings.json is parsed into a base configuration object.
  2. Load workspace settings if present – The project-level file at ./.qwen/settings.json is read only if it exists on disk.
  3. 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.json takes precedence over ~/.qwen/settings.json through a deep-merge operation performed by loadSettings() 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() and Storage.getWorkspaceSettingsPath() in packages/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →