Skip to content

fix: preserve global config during transient init misses#37

Open
kingrubic wants to merge 1 commit into
ZeR020:mainfrom
kingrubic:fix/init-config-preserve-global
Open

fix: preserve global config during transient init misses#37
kingrubic wants to merge 1 commit into
ZeR020:mainfrom
kingrubic:fix/init-config-preserve-global

Conversation

@kingrubic

@kingrubic kingrubic commented Jun 23, 2026

Copy link
Copy Markdown

Summary

Fixes #35.

This keeps the last known good global config available during initConfig() so a transient miss of both global and project config paths does not rebuild CONFIG from hardcoded defaults and break user-configured embedding settings.

The guard only reuses the last global config when the current CONFIG still matches that global config. If the current config includes project overrides and no config files are found, the existing default fallback behavior is preserved.

Verification

  • npm test -- tests/config-resolution.test.ts
  • npm run typecheck
  • npm run format:check
  • repo pre-push DeepSource-equivalent checks passed (warnings were existing/non-blocking)

Note: local pre-commit expected bun, which was not available in this environment; I ran the npm verification commands above and pushed after the repo pre-push checks passed.

Summary by CodeRabbit

  • Bug Fixes

    • Improved configuration resilience by preserving previously loaded settings when configuration sources become temporarily unavailable.
  • Tests

    • Added test coverage for configuration recovery behavior when config paths are inaccessible.

@deepsource-io

deepsource-io Bot commented Jun 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 01fefff...7cc0b7e on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Jun 23, 2026 11:51p.m. Review ↗
Secrets Jun 23, 2026 11:51p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

Comment thread src/config.ts
Comment on lines +588 to +600
function isCurrentConfigFromLastKnownGlobal(): boolean {
if (Object.keys(lastKnownGlobalConfig).length === 0) return false;
const globalConfig = buildConfig(lastKnownGlobalConfig);
return (
CONFIG.embeddingModel === globalConfig.embeddingModel &&
CONFIG.embeddingDimensions === globalConfig.embeddingDimensions &&
CONFIG.embeddingApiUrl === globalConfig.embeddingApiUrl &&
CONFIG.embeddingApiKey === globalConfig.embeddingApiKey &&
CONFIG.opencodeProvider === globalConfig.opencodeProvider &&
CONFIG.opencodeModel === globalConfig.opencodeModel &&
CONFIG.autoCaptureEnabled === globalConfig.autoCaptureEnabled
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected function declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable


It is considered a best practice to avoid 'polluting' the global scope with variables that are intended to be local to the script. Global variables created from a script can produce name collisions with global variables created from another script, which will usually lead to runtime errors or unexpected behavior. It is mostly useful for browser scripts.

Comment thread src/config.ts
const _globalFileConfig = loadConfigFromPaths(CONFIG_FILES);
let lastKnownGlobalConfig = _globalFileConfig;

function isCurrentConfigFromLastKnownGlobal(): boolean {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`isCurrentConfigFromLastKnownGlobal` has a cyclomatic complexity of 8 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

Comment thread src/config.ts
if (Object.keys(lastKnownGlobalConfig).length === 0) return false;
const globalConfig = buildConfig(lastKnownGlobalConfig);
return (
CONFIG.embeddingModel === globalConfig.embeddingModel &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'CONFIG' was used before it was defined


Variables, functions and types should always be used after they've been defined. This issue will flag any code snippets that use variables or types before definition.

Comment thread src/config.ts
const globalConfig = buildConfig(lastKnownGlobalConfig);
return (
CONFIG.embeddingModel === globalConfig.embeddingModel &&
CONFIG.embeddingDimensions === globalConfig.embeddingDimensions &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'CONFIG' was used before it was defined


Variables, functions and types should always be used after they've been defined. This issue will flag any code snippets that use variables or types before definition.

Comment thread src/config.ts
return (
CONFIG.embeddingModel === globalConfig.embeddingModel &&
CONFIG.embeddingDimensions === globalConfig.embeddingDimensions &&
CONFIG.embeddingApiUrl === globalConfig.embeddingApiUrl &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'CONFIG' was used before it was defined


Variables, functions and types should always be used after they've been defined. This issue will flag any code snippets that use variables or types before definition.

Comment thread src/config.ts
CONFIG.embeddingApiUrl === globalConfig.embeddingApiUrl &&
CONFIG.embeddingApiKey === globalConfig.embeddingApiKey &&
CONFIG.opencodeProvider === globalConfig.opencodeProvider &&
CONFIG.opencodeModel === globalConfig.opencodeModel &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'CONFIG' was used before it was defined


Variables, functions and types should always be used after they've been defined. This issue will flag any code snippets that use variables or types before definition.

Comment thread src/config.ts
CONFIG.embeddingApiKey === globalConfig.embeddingApiKey &&
CONFIG.opencodeProvider === globalConfig.opencodeProvider &&
CONFIG.opencodeModel === globalConfig.opencodeModel &&
CONFIG.autoCaptureEnabled === globalConfig.autoCaptureEnabled

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'CONFIG' was used before it was defined


Variables, functions and types should always be used after they've been defined. This issue will flag any code snippets that use variables or types before definition.

});

it("uses the last known global config when config paths are temporarily unavailable", () => {
(globalThis as any).__mockFs.existsSync = (p: unknown) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected any. Specify a different type


The any type can sometimes leak into your codebase. TypeScript compiler skips the type checking of the any typed variables, so it creates a potential safety hole, and source of bugs in your codebase. We recommend using unknown or never type variable.

const path = String(p);
return path.includes(".config/opencode/opencode-mem0");
};
(globalThis as any).__mockFs.readFileSync = () =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected any. Specify a different type


The any type can sometimes leak into your codebase. TypeScript compiler skips the type checking of the any typed variables, so it creates a potential safety hole, and source of bugs in your codebase. We recommend using unknown or never type variable.

expect(CONFIG.embeddingModel).toBe("text-embedding-3-small");
expect(CONFIG.embeddingDimensions).toBe(1536);

(globalThis as any).__mockFs.existsSync = () => false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected any. Specify a different type


The any type can sometimes leak into your codebase. TypeScript compiler skips the type checking of the any typed variables, so it creates a potential safety hole, and source of bugs in your codebase. We recommend using unknown or never type variable.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds lastKnownGlobalConfig state and an isCurrentConfigFromLastKnownGlobal predicate to src/config.ts. Updates initConfig to refresh this snapshot when a non-empty global config loads and to short-circuit with a restore-or-reset path when both global and project configs are empty. A new test validates that CONFIG retains embedding settings after a simulated transient filesystem failure.

Changes

Config resilience on transient file unavailability

Layer / File(s) Summary
lastKnownGlobalConfig state, predicate, initConfig guard, and test
src/config.ts, tests/config-resolution.test.ts
Declares lastKnownGlobalConfig (seeded from module-load global config) and isCurrentConfigFromLastKnownGlobal (compares embedding model/dimensions/api key, provider/model, and autoCaptureEnabled fields). Updates initConfig to refresh that snapshot on successful load and adds an early-return branch when both configs are empty: restores from snapshot if CONFIG still matches, otherwise applies buildConfig({}). New test mocks existsSync/readFileSync to load embedding config, then flips availability off and asserts CONFIG retains the previously loaded values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 When the config file hides away,
My last-known values choose to stay!
No defaults shall sneak in by night,
I guard my embedding dims just right.
lastKnownGlobalConfig — hooray! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: preserve global config during transient init misses' accurately and specifically describes the main change: preventing CONFIG reset during temporary file inaccessibility.
Description check ✅ Passed The description covers all essential sections: summary of the fix, objectives, verification steps, and links to issue #35 with clear context about the problem being solved.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #35: it preserves the last known global config during transient misses, prevents silent CONFIG reset to defaults, and includes test verification of this behavior.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the bug fix: configuration reload behavior in src/config.ts and test coverage in tests/config-resolution.test.ts, with no extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/config.ts`:
- Around line 588-600: The isCurrentConfigFromLastKnownGlobal function calls
buildConfig inside a boolean predicate, which causes side effects (setLogLevel
calls) and can throw exceptions unexpectedly. Additionally, the same
buildConfig(lastKnownGlobalConfig) computation happens again at line 640,
causing duplicate work. Refactor by computing the rebuilt config once at the
call site (in initConfig) and passing the result to
isCurrentConfigFromLastKnownGlobal as a parameter, or restructure the function
to check for emptiness of lastKnownGlobalConfig first before building and
comparing, avoiding the redundant buildConfig calls and isolating side effects
to a single location.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4c40cb3e-4076-4ce5-af02-ffb23bc4cf0d

📥 Commits

Reviewing files that changed from the base of the PR and between 01fefff and 7cc0b7e.

📒 Files selected for processing (2)
  • src/config.ts
  • tests/config-resolution.test.ts

Comment thread src/config.ts
Comment on lines +588 to +600
function isCurrentConfigFromLastKnownGlobal(): boolean {
if (Object.keys(lastKnownGlobalConfig).length === 0) return false;
const globalConfig = buildConfig(lastKnownGlobalConfig);
return (
CONFIG.embeddingModel === globalConfig.embeddingModel &&
CONFIG.embeddingDimensions === globalConfig.embeddingDimensions &&
CONFIG.embeddingApiUrl === globalConfig.embeddingApiUrl &&
CONFIG.embeddingApiKey === globalConfig.embeddingApiKey &&
CONFIG.opencodeProvider === globalConfig.opencodeProvider &&
CONFIG.opencodeModel === globalConfig.opencodeModel &&
CONFIG.autoCaptureEnabled === globalConfig.autoCaptureEnabled
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid the side-effecting buildConfig call inside a boolean predicate, and reuse the result.

isCurrentConfigFromLastKnownGlobal calls buildConfig(lastKnownGlobalConfig) purely to compare fields, but buildConfig calls setLogLevel(fileConfig.logLevel) and throws on invalid config (lines 568-583). Two problems:

  1. A predicate that mutates global log level / can throw is surprising and makes initConfig reset behavior fragile if lastKnownGlobalConfig is ever malformed.
  2. The same buildConfig(lastKnownGlobalConfig) is computed again at line 640, so it runs twice on the restore path.

Consider computing the rebuilt config once and passing it in:

♻️ Proposed refactor
-function isCurrentConfigFromLastKnownGlobal(): boolean {
-  if (Object.keys(lastKnownGlobalConfig).length === 0) return false;
-  const globalConfig = buildConfig(lastKnownGlobalConfig);
-  return (
+function isCurrentConfigFromLastKnownGlobal(globalConfig: ReturnType<typeof buildConfig>): boolean {
+  return (
     CONFIG.embeddingModel === globalConfig.embeddingModel &&
     CONFIG.embeddingDimensions === globalConfig.embeddingDimensions &&
     CONFIG.embeddingApiUrl === globalConfig.embeddingApiUrl &&
     CONFIG.embeddingApiKey === globalConfig.embeddingApiKey &&
     CONFIG.opencodeProvider === globalConfig.opencodeProvider &&
     CONFIG.opencodeModel === globalConfig.opencodeModel &&
     CONFIG.autoCaptureEnabled === globalConfig.autoCaptureEnabled
   );
 }

Then in the guard, build once and branch on emptiness:

-    if (isCurrentConfigFromLastKnownGlobal()) {
-      Object.assign(CONFIG, buildConfig(lastKnownGlobalConfig));
-    } else {
-      Object.assign(CONFIG, buildConfig({}));
-    }
+    const lastKnown =
+      Object.keys(lastKnownGlobalConfig).length > 0 ? buildConfig(lastKnownGlobalConfig) : null;
+    if (lastKnown && isCurrentConfigFromLastKnownGlobal(lastKnown)) {
+      Object.assign(CONFIG, lastKnown);
+    } else {
+      Object.assign(CONFIG, buildConfig({}));
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.ts` around lines 588 - 600, The isCurrentConfigFromLastKnownGlobal
function calls buildConfig inside a boolean predicate, which causes side effects
(setLogLevel calls) and can throw exceptions unexpectedly. Additionally, the
same buildConfig(lastKnownGlobalConfig) computation happens again at line 640,
causing duplicate work. Refactor by computing the rebuilt config once at the
call site (in initConfig) and passing the result to
isCurrentConfigFromLastKnownGlobal as a parameter, or restructure the function
to check for emptiness of lastKnownGlobalConfig first before building and
comparing, avoiding the redundant buildConfig calls and isolating side effects
to a single location.

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.

fix: initConfig() silently resets CONFIG to defaults when global config file is transiently inaccessible

1 participant