- Home
- Documentation
- configuration
- Configuration Discovery and Resolution
Configuration Discovery and Resolution
Configuration Discovery and Resolution
Section titled “Configuration Discovery and Resolution”This document describes how the coding-agent resolves configuration today: which roots are scanned, how precedence works, and how resolved config is consumed by settings, skills, hooks, tools, and extensions.
Primary implementation:
src/config.tssrc/config/settings.tssrc/config/settings-schema.tssrc/discovery/builtin.tssrc/discovery/helpers.ts
Key integration points:
src/capability/index.tssrc/discovery/index.tssrc/extensibility/skills.tssrc/extensibility/hooks/loader.tssrc/extensibility/custom-tools/loader.tssrc/extensibility/extensions/loader.ts
Resolution flow (visual)
Section titled “Resolution flow (visual)” Config roots (ordered)┌───────────────────────────────────────┐│ 1) ~/.xcsh/agent + <cwd>/.xcsh ││ 2) ~/.claude + <cwd>/.claude ││ 3) ~/.codex + <cwd>/.codex ││ 4) ~/.gemini + <cwd>/.gemini │└───────────────────────────────────────┘ │ ▼ config.ts helper resolution (getConfigDirs/findConfigFile/findNearest...) │ ▼ capability providers enumerate items (native, claude, codex, gemini, agents, etc.) │ ▼ priority sort + per-capability dedup │ ▼ subsystem-specific consumption (settings, skills, hooks, tools, extensions)1) Config roots and source order
Section titled “1) Config roots and source order”Canonical roots
Section titled “Canonical roots”src/config.ts defines a fixed source priority list:
.xcsh(native).claude.codex.gemini
User-level bases:
~/.xcsh/agent~/.claude~/.codex~/.gemini
Project-level bases:
<cwd>/.xcsh<cwd>/.claude<cwd>/.codex<cwd>/.gemini
CONFIG_DIR_NAME is .xcsh (packages/utils/src/dirs.ts).
Important constraint
Section titled “Important constraint”The generic helpers in src/config.ts do not include .pi in source discovery order.
2) Core discovery helpers (src/config.ts)
Section titled “2) Core discovery helpers (src/config.ts)”getConfigDirs(subpath, options)
Section titled “getConfigDirs(subpath, options)”Returns ordered entries:
- User-level entries first (by source priority)
- Then project-level entries (by same source priority)
Options:
user(defaulttrue)project(defaulttrue)cwd(defaultgetProjectDir())existingOnly(defaultfalse)
This API is used for directory-based config lookups (commands, hooks, tools, agents, etc.).
findConfigFile(subpath, options) / findConfigFileWithMeta(...)
Section titled “findConfigFile(subpath, options) / findConfigFileWithMeta(...)”Searches for the first existing file across ordered bases, returns first match (path-only or path+metadata).
findAllNearestProjectConfigDirs(subpath, cwd)
Section titled “findAllNearestProjectConfigDirs(subpath, cwd)”Walks parent directories upward and returns the nearest existing directory per source base (.xcsh, .claude, .codex, .gemini), then sorts results by source priority.
Use this when project config should be inherited from ancestor directories (monorepo/nested workspace behavior).
3) File config wrapper (ConfigFile<T> in src/config.ts)
Section titled “3) File config wrapper (ConfigFile<T> in src/config.ts)”ConfigFile<T> is the schema-validated loader for single config files.
Supported formats:
.yml/.yaml.json/.jsonc
Behavior:
- Validates parsed data with AJV against a provided TypeBox schema.
- Caches load result until
invalidate(). - Returns tri-state result via
tryLoad():oknot-founderror(ConfigErrorwith schema/parse context)
Legacy migration still supported:
- If target path is
.yml/.yaml, a sibling.jsonis auto-migrated once (migrateJsonToYml).
4) Settings resolution model (src/config/settings.ts)
Section titled “4) Settings resolution model (src/config/settings.ts)”The runtime settings model is layered:
- Global settings:
~/.xcsh/agent/config.yml - Project settings: discovered via settings capability (
settings.jsonfrom providers) - Runtime overrides: in-memory, non-persistent
- Schema defaults: from
SETTINGS_SCHEMA
Effective read path:
defaults <- global <- project <- overrides
Write behavior:
settings.set(...)writes to the global layer (config.yml) and queues background save.- Project settings are read-only from capability discovery.
Migration behavior still active
Section titled “Migration behavior still active”On startup, if config.yml is missing:
- Migrate from
~/.xcsh/agent/settings.json(renamed to.bakon success) - Merge with legacy DB settings from
agent.db - Write merged result to
config.yml
Field-level migrations in #migrateRawSettings:
queueMode->steeringModeask.timeoutmilliseconds -> seconds when old value looks like ms (> 1000)- Legacy flat
theme: "..."->theme.dark/theme.lightstructure
5) Capability/discovery integration
Section titled “5) Capability/discovery integration”Most non-core config loading flows through the capability registry (src/capability/index.ts + src/discovery/index.ts).
Provider ordering
Section titled “Provider ordering”Providers are sorted by numeric priority (higher first). Example priorities:
- Native OMP (
builtin.ts):100 - Claude:
80 - Codex / agents / Claude marketplace:
70 - Gemini:
60
Provider precedence (higher wins)
native (.xcsh) priority 100claude priority 80codex / agents / ... priority 70gemini priority 60Dedup semantics
Section titled “Dedup semantics”Capabilities define a key(item):
- same key => first item wins (higher-priority/earlier-loaded item)
- no key (
undefined) => no dedup, all items retained
Relevant keys:
- skills:
name - tools:
name - hooks:
${type}:${tool}:${name} - extension modules:
name - extensions:
name - settings: no dedup (all items preserved)
6) Native .xcsh provider behavior (src/discovery/builtin.ts)
Section titled “6) Native .xcsh provider behavior (src/discovery/builtin.ts)”Native provider (id: native) reads from:
- project:
<cwd>/.xcsh/... - user:
~/.xcsh/agent/...
Directory admission rule
Section titled “Directory admission rule”builtin.ts only includes a config root if the directory exists and is non-empty (ifNonEmptyDir).
Scope-specific loading
Section titled “Scope-specific loading”- Skills:
skills/*/SKILL.md - Slash commands:
commands/*.md - Rules:
rules/*.{md,mdc} - Prompts:
prompts/*.md - Instructions:
instructions/*.md - Hooks:
hooks/pre/*,hooks/post/* - Tools:
tools/*.json|*.mdandtools/<name>/index.ts - Extension modules: discovered under
extensions/(+ legacysettings.json.extensionsstring array) - Extensions:
extensions/<name>/gemini-extension.json - Settings capability:
settings.json
Nearest-project lookup nuance
Section titled “Nearest-project lookup nuance”For SYSTEM.md and AGENTS.md, native provider uses nearest-ancestor project .xcsh directory search (walk-up) but still requires the .xcsh dir to be non-empty.
7) How major subsystems consume config
Section titled “7) How major subsystems consume config”Settings subsystem
Section titled “Settings subsystem”Settings.init()loads globalconfig.yml+ discovered projectsettings.jsoncapability items.- Only capability items with
level === "project"are merged into project layer.
Skills subsystem
Section titled “Skills subsystem”extensibility/skills.tsloads vialoadCapability(skillCapability.id, { cwd }).- Applies source toggles and filters (
ignoredSkills,includeSkills, custom dirs). - Legacy-named toggles still exist (
skills.enablePiUser,skills.enablePiProject) but they gate the native provider (provider === "native").
Hooks subsystem
Section titled “Hooks subsystem”discoverAndLoadHooks()resolves hook paths from hook capability + explicit configured paths.- Then loads modules via Bun import.
Tools subsystem
Section titled “Tools subsystem”discoverAndLoadCustomTools()resolves tool paths from tool capability + plugin tool paths + explicit configured paths.- Declarative
.md/.jsontool files are metadata only; executable loading expects code modules.
Extensions subsystem
Section titled “Extensions subsystem”discoverAndLoadExtensions()resolves extension modules from extension-module capability plus explicit paths.- Current implementation intentionally keeps only capability items with
_source.provider === "native"before loading.
8) Precedence rules to rely on
Section titled “8) Precedence rules to rely on”Use this mental model:
- Source directory ordering from
config.tsdetermines candidate path order. - Capability provider priority determines cross-provider precedence.
- Capability key dedup determines collision behavior (first wins for keyed capabilities).
- Subsystem-specific merge logic can further change effective precedence (especially settings).
Settings-specific caveat
Section titled “Settings-specific caveat”Settings capability items are not deduplicated; Settings.#loadProjectSettings() deep-merges project items in returned order. Because merge applies later item values over earlier values, effective override behavior depends on provider emission order, not just capability key semantics.
9) Legacy/compatibility behaviors still present
Section titled “9) Legacy/compatibility behaviors still present”ConfigFileJSON -> YAML migration for YAML-targeted files.- Settings migration from
settings.jsonandagent.dbtoconfig.yml. - Settings key migrations (
queueMode,ask.timeout, flattheme). - Extension manifest compatibility: loader accepts both
package.json.xcshandpackage.json.pimanifest sections. - Legacy setting names
skills.enablePiUser/skills.enablePiProjectare still active gates for native skill source.
If these compatibility paths are removed in code, update this document immediately; several runtime behaviors still depend on them today.