Skip to content

Fix: treat DEFAULT_AI_ASSISTANT as a fallback default, not a hard override#1919

Merged
Wirasm merged 3 commits into
devfrom
archon/task-fix-issue-1171
Jun 8, 2026
Merged

Fix: treat DEFAULT_AI_ASSISTANT as a fallback default, not a hard override#1919
Wirasm merged 3 commits into
devfrom
archon/task-fix-issue-1171

Conversation

@Wirasm

@Wirasm Wirasm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Problem: DEFAULT_AI_ASSISTANT env var unconditionally overwrites config.assistant in applyEnvOverrides(), silently masking any value saved via the Web UI Settings page.
  • Why it matters: In Docker deployments, users who save "Default Assistant = Claude" via Settings see the change reflected until they reload — then it reverts to whatever DEFAULT_AI_ASSISTANT is set to. The save is effectively a no-op with no error shown.
  • What changed: applyEnvOverrides() now treats DEFAULT_AI_ASSISTANT as a fallback default — it only applies when neither the global config nor the repo config has an explicit assistant set. Explicit config-file values win.
  • What did not change: Streaming mode env vars (TELEGRAM_STREAMING_MODE, etc.) still behave as hard overrides — they have no UI-save path, so no fallback guard is needed there.

UX Journey

Before

User (Docker, DEFAULT_AI_ASSISTANT=codex)
────
Opens Settings ──────────▶  GET /api/config → { assistant: "codex" }
Selects Claude  ──────────▶  PATCH /api/config → writes defaultAssistant: claude to config.yaml ✅
Reloads page    ──────────▶  GET /api/config → { assistant: "codex" }  ← bug: env var wins again
User sees "codex" selected despite having saved "claude"

After

User (Docker, DEFAULT_AI_ASSISTANT=codex)
────
Opens Settings ──────────▶  GET /api/config → { assistant: "codex" }   (env fallback, no config yet)
Selects Claude  ──────────▶  PATCH /api/config → writes defaultAssistant: claude to config.yaml ✅
Reloads page    ──────────▶  GET /api/config → { assistant: "claude" }  ← [fixed: config file wins]
User sees "claude" selected — save is persistent

Architecture Diagram

Before

loadConfig()
  ├── loadGlobalConfig()           reads ~/.archon/config.yaml
  ├── mergeGlobalConfig()          sets config.assistant = globalConfig.defaultAssistant
  ├── loadRepoConfig() [optional]
  ├── mergeRepoConfig() [optional]
  └── applyEnvOverrides(config)    UNCONDITIONALLY sets config.assistant = DEFAULT_AI_ASSISTANT
                                   ↑ always wins regardless of saved config

After

loadConfig()
  ├── loadGlobalConfig()           reads ~/.archon/config.yaml
  ├── mergeGlobalConfig()          sets config.assistant = globalConfig.defaultAssistant
  ├── loadRepoConfig() [optional]  captured as repoConfig
  ├── mergeRepoConfig() [optional]
  └── applyEnvOverrides(config, globalConfig, repoConfig)
        [~] hasExplicitConfig = globalConfig.defaultAssistant || repoConfig.assistant
        [~] if (!hasExplicitConfig) → apply env var  (fallback only)
            if (hasExplicitConfig)  → skip env var   (config file wins)

Connection inventory:

From To Status Notes
loadConfig applyEnvOverrides modified now passes globalConfig + repoConfig
applyEnvOverrides process.env.DEFAULT_AI_ASSISTANT modified guarded by hasExplicitConfig
All other callers / env vars in applyEnvOverrides unchanged unchanged streaming, concurrency, bot-name overrides unaffected

Label Snapshot

  • Risk: risk: low
  • Size: size: XS
  • Scope: core:config
  • Module: core:config-loader

Change Metadata

  • Change type: bug
  • Primary scope: core

Linked Issue

Validation Evidence (required)

bun run validate

All seven checks passed on the first run:

Check Result
check:bundled ✅ (36 commands, 20 workflows — up to date)
check:bundled-skill ✅ (23 files across 2 skills — up to date)
check:bundled-schema
type-check ✅ (all 10 packages)
lint ✅ (0 errors, 0 warnings)
format:check
test ✅ (all packages; config-loader.test.ts: 46 pass / 0 fail / 76 expect calls)
  • Evidence provided: full bun run validate run captured in validation artifact
  • No commands skipped

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No

The change only affects the precedence of a config-file value versus an env var — no new surfaces, no secret handling.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? Yes — semantics of DEFAULT_AI_ASSISTANT change: it is now a fallback, not a hard override. Operators who relied on the env var to permanently lock the assistant (even against user saves) will see different behavior. The expected and documented use case (provide a default for fresh installs) continues to work correctly.
  • Database migration needed? No

Human Verification (required)

  • Verified scenarios: unit tests cover (C1) env applies when no config sets assistant, (C2) global config assistant wins over env, (C3) repo config assistant wins over env
  • Edge cases checked: invalid provider in env + config set → env skipped entirely (no throw); no config + no env → default 'claude' from getDefaults() unchanged
  • What was not verified: end-to-end round-trip via a live Docker container with DEFAULT_AI_ASSISTANT=codex — covered by unit tests at the same precedence layer

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: loadConfig() — called by every API handler that reads config (GET /api/config, PATCH /api/config/assistants, workflow routes)
  • Potential unintended effects: none anticipated; only the DEFAULT_AI_ASSISTANT precedence is changed; all other env overrides in applyEnvOverrides are unchanged
  • Guardrails/monitoring for early detection: the three new/updated unit tests act as a regression guard

Rollback Plan (required)

  • Fast rollback: git revert e847dad6 restores the unconditional override
  • Feature flags or config toggles: none
  • Observable failure symptoms: GET /api/config returns env-var provider instead of saved provider after a UI save

Risks and Mitigations

  • Risk: operators who used DEFAULT_AI_ASSISTANT as a hard lock (preventing users from changing the assistant) will lose that capability
    • Mitigation: this was never documented behavior; the env var is documented as a "default". The correct way to lock a value is to not expose the Settings UI or to enforce it at the config-file level. The new behavior matches the documented intent.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed DEFAULT_AI_ASSISTANT environment variable to correctly function as a fallback only when no config file explicitly sets an assistant, ensuring configuration files take proper precedence.
  • Documentation

    • Updated configuration reference to clarify DEFAULT_AI_ASSISTANT fallback behavior and precedence rules with config settings.

…rride (#1171)

In Docker, saving "Default Assistant = Claude" in the Web UI Settings persisted
the YAML change, but every subsequent loadConfig() call re-applied the
DEFAULT_AI_ASSISTANT env var as a hard override and discarded the saved
preference. The Settings save appeared to work but was silently masked on
every page reload.

Changes:
- applyEnvOverrides now accepts the raw globalConfig and repoConfig and only
  applies DEFAULT_AI_ASSISTANT when neither file explicitly set the assistant
- loadConfig passes the raw config sources into applyEnvOverrides
- Updated the existing test to assert the corrected precedence (config-file
  wins over env var) and added regression tests for the env-var-as-fallback
  path and for repo-config precedence over the env var

Fixes #1171
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 75ff6ac0-86f1-46db-84fa-8caaa2f9c26e

📥 Commits

Reviewing files that changed from the base of the PR and between 282e9ff and 8f0846b.

📒 Files selected for processing (3)
  • packages/core/src/config/config-loader.test.ts
  • packages/core/src/config/config-loader.ts
  • packages/docs-web/src/content/docs/reference/configuration.md

📝 Walkthrough

Walkthrough

This PR resolves the issue where DEFAULT_AI_ASSISTANT overrode user-saved configuration in the Settings UI. The environment variable is now a true fallback that applies only when neither the global nor repo config files explicitly set an assistant. When config files do set an assistant, invalid DEFAULT_AI_ASSISTANT values are silently ignored with a warning instead of throwing an error, while validation still enforces strict constraints when the environment variable is actually applied as the fallback.

Changes

Assistant fallback and precedence

Layer / File(s) Summary
Precedence logic and config integration
packages/core/src/config/config-loader.ts
applyEnvOverrides now accepts globalConfig and repoConfig parameters to determine whether DEFAULT_AI_ASSISTANT should apply as a fallback or be ignored. When no explicit assistant is present in either config file, the fallback is applied and validated strictly; when an explicit assistant exists, invalid values are logged as warnings instead of throwing. loadConfig was refactored to pass both config objects through and extract repoConfig separately.
Fallback behavior and validation tests
packages/core/src/config/config-loader.test.ts
Tests verify DEFAULT_AI_ASSISTANT is applied as fallback when global config lacks an assistant, does not override repo config settings (with mocked file reads for path-specific control), and validates strictly as fallback but is silently ignored when a config file sets the assistant.
Configuration reference documentation
packages/docs-web/src/content/docs/reference/configuration.md
DEFAULT_AI_ASSISTANT entry clarifies fallback semantics, override relationships with defaultAssistant (global) and assistant (repo), and provider-id validation constraint.

Sequence Diagram

sequenceDiagram
  participant User
  participant SettingsUI
  participant LoadConfig
  participant GlobalConfig as Global Config File
  participant RepoConfig as Repo Config File
  participant EnvVar as DEFAULT_AI_ASSISTANT
  
  User->>SettingsUI: Save Default Assistant = Claude
  SettingsUI->>GlobalConfig: Write defaultAssistant: claude
  
  User->>SettingsUI: Reload Settings Page
  SettingsUI->>LoadConfig: Load config
  LoadConfig->>GlobalConfig: Read global config (has defaultAssistant)
  LoadConfig->>RepoConfig: Read repo config
  LoadConfig->>EnvVar: Check DEFAULT_AI_ASSISTANT
  EnvVar-->>LoadConfig: Ignore (config file has explicit assistant)
  LoadConfig-->>SettingsUI: Return claude as assistant
  SettingsUI->>User: Display Claude as selected
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • coleam00/Archon#1746: Both PRs modify DEFAULT_AI_ASSISTANT/defaultAssistant resolution in loadConfig—this PR changes fallback precedence and validation, directly affecting downstream assistant type defaulting in createCodebase.
  • coleam00/Archon#1729: This PR's changes to loadConfig fallback behavior affect resolveDefaultAssistant, which adapters use to set assistant type during codespace registration.

Poem

🐰 A fallback befalls, but defers to the wise,
When config takes hold, the env just complies,
No shadowed defaults when users decide,
The settings now stick—let the configs preside! 🌿

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch archon/task-fix-issue-1171

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Wirasm

Wirasm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Comprehensive PR Review

PR: #1919 — Fix: treat DEFAULT_AI_ASSISTANT as a fallback default, not a hard override
Reviewed by: 3 specialized agents (code-review, error-handling, test-coverage)
Date: 2026-06-08


Summary

Minimal, well-scoped fix (2 files, +64/-14). applyEnvOverrides() now correctly treats DEFAULT_AI_ASSISTANT as a fallback that config files can override. The hasExplicitConfig guard is correctly derived from pre-merge config objects, three new tests cover all primary paths, and no public API surface changed.

Verdict: REQUEST_CHANGES — two MEDIUM issues, both small targeted fixes

Severity Count
🔴 CRITICAL 0
🟠 HIGH 0
🟡 MEDIUM 2
🟢 LOW 2

🟡 Medium Issues

M1: Invalid DEFAULT_AI_ASSISTANT silently discarded when config file present

📍 packages/core/src/config/config-loader.ts:360-373

When hasExplicitConfig=true, the isRegisteredProvider check is bypassed entirely. An invalid env var value (DEFAULT_AI_ASSISTANT=typo-provider) is silently dropped with no log. Two concrete failure modes:

  1. A typo in the env var goes undetected indefinitely — user believes env var is active
  2. User removes the config file entry expecting the env var to take over → sudden startup crash from an env var that was always invalid
View recommended fix (~5 lines)

Add an else if branch after the !hasExplicitConfig block:

} else if (!isRegisteredProvider(envAssistant)) {
  // Config file takes precedence, but warn that the env var value is unknown —
  // a typo here would go undetected if we don't surface it.
  getLog().warn(
    { envAssistant, available: getRegisteredProviderNames() },
    'config.env_assistant_unknown_ignored'
  );
}

Precedent: same getLog().warn(..., 'config.xxx_ignored') pattern used at config-loader.ts:514-516 for whitespace in docs.path.


M2: Silent-skip of invalid env var when config IS set — untested

📍 packages/core/src/config/config-loader.test.ts

No test asserts that DEFAULT_AI_ASSISTANT=nonexistent-provider does NOT throw when a config file wins. The behavior is intentional, but without a test a future refactor could accidentally restore the old throw-on-invalid behavior — breaking users who have a stale env var alongside a config file.

View recommended test (~8 lines)
test('invalid DEFAULT_AI_ASSISTANT env var is silently ignored when config file sets assistant', async () => {
  mockFsReadFile.mockResolvedValue('defaultAssistant: claude\n');
  process.env.DEFAULT_AI_ASSISTANT = 'nonexistent-provider';

  // Should not throw even though env has an invalid provider
  const config = await loadConfig();
  expect(config.assistant).toBe('claude');
});

This is the complement to the already-preserved 'throws on unknown DEFAULT_AI_ASSISTANT env var' test (which uses an empty config → hasExplicitConfig=false → throws). Together they express the full contract.


🟢 Low Issues

View 2 low-priority suggestions
Issue Location Suggestion
Docs don't describe new precedence docs/reference/configuration.md:253 Change "Default AI assistant" → "Fallback AI assistant when no config file sets one. Overridden by defaultAssistant in global config or assistant in repo config."
globalRead flag in new test config-loader.test.ts:270 Informational only — consistent with the pre-existing convention used throughout the test file (lines 329, 353, 444, 490, 642). No change needed.

✅ What's Good

  • hasExplicitConfig derived from raw (pre-merge) config objects — correctly answers "did the user explicitly configure this?" rather than "does the merged config have a value?", survives future priority changes cleanly
  • Three tests cover the full behavioral matrix: env-only (applies), global-config-wins, repo-config-wins — all targeted and resilient to refactor
  • Existing throw-path test preserved unchanged — still fires correctly because it uses an empty config (hasExplicitConfig=false)
  • Minimal blast radiusapplyEnvOverrides is module-private; no public API surface changed
  • Streaming overrides intentionally left as hard overrides — consistent with the correct scope decision
  • Type safety: new globalConfig?: GlobalConfig and repoConfig?: RepoConfig params correctly typed; no any
  • No new mock.module() calls — no Bun mock pollution risk

Next Steps

  1. Fix M1: add the else if (!isRegisteredProvider(envAssistant)) WARN branch
  2. Fix M2: add the "invalid env ignored when config IS set" test
  3. Both are ~5-8 lines each; ready to approve immediately after
  4. Docs update (LOW) can be a separate PR

Reviewed by Archon comprehensive-pr-review workflow

- Add WARN log when DEFAULT_AI_ASSISTANT has an invalid provider name
  but a config file is present (typo would otherwise go undetected)
- Add test asserting invalid env var is silently ignored when config IS set
- Update docs to describe DEFAULT_AI_ASSISTANT as a fallback, not override
@Wirasm

Wirasm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

⚡ Self-Fix Report (Aggressive)

Status: COMPLETE
Pushed: ✅ Changes pushed to archon/task-fix-issue-1171
Commit: 2c6e37e
Philosophy: Fix everything unless clearly a new concern


Fixes Applied (3 total)

Severity Count
🔴 CRITICAL 0
🟠 HIGH 0
🟡 MEDIUM 2
🟢 LOW 1
View all fixes
  • Invalid env var silently discarded when config file present (config-loader.ts:363) — Added else if (!isRegisteredProvider(envAssistant)) WARN log branch so typos in DEFAULT_AI_ASSISTANT are surfaced even when a config file overrides them
  • Silent-skip of invalid env var untested (config-loader.test.ts) — Added test 'invalid DEFAULT_AI_ASSISTANT env var is silently ignored when config file sets assistant' to guard against future regressions
  • Docs don't describe new precedence (configuration.md:253) — Updated description from "Default AI assistant" to "Fallback AI assistant when no config file sets the assistant. Overridden by defaultAssistant in global config or assistant in repo config."

Tests Added

  • packages/core/src/config/config-loader.test.ts: invalid DEFAULT_AI_ASSISTANT env var is silently ignored when config file sets assistant

Skipped (1)

Finding Reason
globalRead flag in new test Informational only — consistent with pre-existing convention throughout the test file (lines 329, 353, 444, 490, 642). No change needed.

Suggested Follow-up Issues

(none)


Validation

✅ Type check | ✅ Lint | ✅ Tests (47 passed)


Self-fix by Archon · aggressive mode · fixes pushed to archon/task-fix-issue-1171

@Wirasm Wirasm marked this pull request as ready for review June 8, 2026 18:58
@Wirasm Wirasm merged commit ba0cfd5 into dev Jun 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Settings default assistant save is masked by DEFAULT_AI_ASSISTANT in Docker

1 participant