How Continue Merges Configuration from Multiple Sources: A Deep Dive into the Config System
Continue combines settings from CLI flags, local files, saved URIs, and remote defaults into a single AssistantUnrolled object using deterministic precedence rules, while supporting injected packages and deduplicating overlapping blocks via the mergeUnrolledAssistants algorithm.
Continue is an open-source AI code assistant that supports complex configuration scenarios requiring settings to be composed from multiple origins. Understanding how Continue handles configuration merging from multiple sources is essential for developers customizing models, rules, and environment variables across different deployment contexts.
Configuration Sources and Precedence Order
The configuration loading process begins in extensions/cli/src/configLoader.ts, where the loadConfiguration function determines which source to use based on a strict priority hierarchy. When multiple sources exist, the system selects the highest-priority option and ignores lower-priority alternatives.
The precedence order is:
- CLI flag (
--config): Specified viacliConfigPathparameter - Saved URI: Stored in authentication config at
~/.continue/config.yaml - Local config.yaml: Default path check using
fs.existsSync(defaultConfigPath) - Remote default config: Fallback when no local file exists
Each source is loaded through specialized helpers like loadFromCliFlag, loadFromSavedUri, loadLocalConfigYaml, or loadDefaultConfig, which eventually invoke unrollAssistantWithConfig to resolve the raw configuration into an AssistantUnrolled object.
Injecting Additional Configuration Blocks
Beyond static files, Continue supports dynamic injection of configuration blocks through CLI options. The ConfigService class in extensions/cli/src/services/ConfigService.ts processes "inject blocks" represented as PackageIdentifier arrays.
The injection workflow follows three steps:
getAdditionalBlocksFromOptionsextracts injected and additional blocks from CLI argumentsunrollPackageIdentifiersAsConfigYamlconverts package identifiers into a temporaryAssistantUnrolledcontaining only the injected blocksmergeUnrolledAssistantscombines the base configuration with the injected blocks
This approach allows users to augment existing configurations with additional models, MCP servers, or rules without modifying the underlying config files.
The Configuration Merge Algorithm
All merging logic resides in packages/config-yaml/src/load/merge.ts within the mergeUnrolledAssistants function. This implementation handles deep merging of environment variables, request options, and deduplication of configuration blocks.
Environment and Request Options Merging
The function performs shallow merges for top-level properties:
- Environment variables: Combined using spread syntax
{ ...current.env, ...incoming.env }, where incoming values override existing ones - Request options: Merged via
mergeConfigYamlRequestOptionsto combine HTTP client settings from multiple sources
Block Deduplication Strategy
For array-based configuration blocks (models, rules, prompts, etc.), the system uses BlockDuplicationDetector to ensure uniqueness while preserving precedence:
const duplicationDetector = new BlockDuplicationDetector();
for (const blockType of BLOCK_TYPES) {
const allOfType = [...(incoming[blockType] ?? []), ...(current[blockType] ?? [])];
const deduplicated = [];
for (const block of allOfType) {
if (block && !duplicationDetector.isDuplicated(block, blockType)) {
deduplicated.push(block);
}
}
assistant[blockType] = deduplicated.length ? (deduplicated as any) : undefined;
}
The algorithm places incoming blocks before current blocks in the concatenation array, ensuring that injected or higher-priority configurations take precedence when duplicates are detected.
Finalizing and Persisting Configuration
After merging, loadConfiguration persists the source URI to ~/.continue/config.yaml, enabling subsequent CLI invocations to reuse the same configuration without explicit flags. The ConfigService then enriches the merged configuration by adding markdown-derived rules, injecting a default chat model if none is configured, and storing the final AssistantUnrolled object in the service container for application-wide access.
Practical Implementation Examples
Loading Configuration with CLI Flags and Injection
import { loadConfiguration } from "./configLoader.js";
import { decodePackageIdentifier } from "@continuedev/config-yaml";
const auth = await loadAuthConfig();
const api = await createApiClient(auth);
const injected = [decodePackageIdentifier("anthropic/claude-sonnet-4-6")];
const { config } = await loadConfiguration(
auth,
"./my-config.yaml", // CLI --config flag path
api,
injected,
false,
);
console.log(config.models); // Injected model plus file-defined models
Manual Configuration Merging
import { mergeUnrolledAssistants } from "@continuedev/config-yaml";
const base = await loadConfigYaml("~/.continue/config.yaml");
const extra = await loadConfigYaml("./extra.yaml");
const merged = mergeUnrolledAssistants(base, extra);
console.log(merged.rules); // Duplicates removed, environments combined
Summary
- Deterministic precedence: CLI flags override saved URIs, which override local files, which override remote defaults according to the logic in
configLoader.ts - Injection support: Package identifiers are converted to temporary configs and merged with base configurations using
mergeUnrolledAssistants - Intelligent deduplication:
BlockDuplicationDetectorensures identical blocks from different sources appear only once, with incoming blocks taking precedence over existing ones - Shallow merging: Environment variables and request options use simple object spreads, with later values overriding earlier ones
- Persistence: Selected configuration sources are saved to
~/.continue/config.yamlfor subsequent CLI sessions
Frequently Asked Questions
What happens if I specify both a CLI config flag and a local config.yaml?
The CLI flag takes precedence. In configLoader.ts, the system checks if (cliConfigPath) return { type: "cli-flag", path: cliConfigPath } before evaluating local files, ensuring explicit command-line arguments always win over disk-based configurations.
How does Continue handle duplicate model definitions from different sources?
The BlockDuplicationDetector class in merge.ts identifies duplicate blocks by type and content. When the same model appears in both injected packages and local configuration, the injected version appears first in the merged array and the duplicate is removed, giving priority to the injected definition according to the concatenation order [...incoming, ...current].
Can environment variables from multiple configs be combined?
Yes. The merge algorithm uses object spreading { ...current.env, ...incoming.env } to shallow merge environment variables. If the same variable exists in both configurations, the incoming value overrides the current value, following standard JavaScript object merge behavior.
Where does Continue store the last used configuration source?
After loading, loadConfiguration persists the source URI to ~/.continue/config.yaml. This saved URI serves as the second-highest priority source in subsequent CLI runs, allowing the system to remember your preferred configuration between sessions without requiring the --config flag every time.
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 →