Skip to content

feat: updater runtime flags and Sonar quality cleanup#42

Merged
Mehdi-Bl merged 3 commits into
mainfrom
feat/updater-planning-squash
Feb 10, 2026
Merged

feat: updater runtime flags and Sonar quality cleanup#42
Mehdi-Bl merged 3 commits into
mainfrom
feat/updater-planning-squash

Conversation

@Mehdi-Bl

@Mehdi-Bl Mehdi-Bl commented Feb 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add updater runtime/options plumbing with OpenFeature-driven flag normalization
  • tighten renderer/main quality findings and clean Sonar maintainability issues
  • refresh Sonar scan flow to always generate fresh lcov coverage before upload

Validation

  • npm run lint
  • npm test -- --runInBand
  • npm run sonar (quality gate OK, coverage uploaded)

Summary by Sourcery

Introduce a feature-flag–driven Electron auto-updater runtime, harden main/renderer logic for better safety and maintainability, and ensure CI releases and Sonar scans use consistent, fresh build metadata and coverage.

New Features:

  • Add a configurable updater runtime for the desktop app with environment/OpenFeature-driven flags and main/renderer IPC endpoints to query status and trigger update checks.

Bug Fixes:

  • Tighten asset protocol handling and repository processing to avoid path traversal, invalid file entries, and unsafe error handling.
  • Refine secret-scanning rules to reduce false positives while still catching sensitive assignments.
  • Normalize various path and token handling utilities to avoid edge-case failures on different platforms.

Enhancements:

  • Refactor repository processing, token counting, content processing, gitignore parsing, and file filtering into smaller, more testable helpers with clearer option handling and safer defaults.
  • Standardize renderer access to window/electron APIs, dark-mode detection, and UI state helpers to improve robustness and testability.
  • Improve XML/markdown export formatting and numeric parsing for more reliable output across formats.

Build:

  • Update release workflow artifacts to include all updater metadata files, enforce tag/package version alignment, and generate fresh Jest coverage during Sonar scans before upload.

CI:

  • Ensure Sonar scan script always regenerates coverage (with an opt-out flag) and fails fast when coverage generation or discovery breaks.

Tests:

  • Extend integration tests for the main process to cover the new updater and asset protocol behavior, add unit suites for the updater and feature flag logic, and adjust existing tests for updated secret-scanner behavior and Electron APIs.

Summary by CodeRabbit

  • New Features

    • Automatic update checks with alpha/stable channel support and manual update status/check actions available in the app.
    • Dark mode initialization made more deterministic for consistent theme behavior.
  • Chores

    • Release workflow now validates tag vs package version and refines draft/prerelease logic.
    • Dependency and test infra updates plus CI/reporting improvements.

@sourcery-ai

This comment has been minimized.

@github-actions

This comment has been minimized.

@coderabbitai

This comment has been minimized.

@qodo-free-for-open-source-projects

This comment has been minimized.

@gemini-code-assist

This comment has been minimized.

@sourcery-ai sourcery-ai 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.

Hey - I've found 4 issues, and left some high level feedback:

  • Updater-related types (e.g., UpdaterStatus, UpdateCheckResult, UpdaterChannel/State) are now defined both in src/types/ipc.ts and src/main/updater.ts; consider consolidating these into a single shared definition to avoid divergence between main and renderer contracts.
  • There is repeated error/boolean/env parsing logic (e.g., TRUE_VALUES + parseBooleanEnv, getErrorMessage) across main/index.ts, src/main/updater.ts, and src/main/feature-flags.ts; extracting these into a small shared utility would reduce duplication and keep behavior consistent.
  • The assets protocol fallback (createForbiddenAssetResponse) returns a plain object cast as Response when Response is not defined, but protocol.handle expects a real Fetch Response; it might be more robust to ensure a proper Response implementation is always available in the main process (or to avoid the casted object path entirely).
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Updater-related types (e.g., UpdaterStatus, UpdateCheckResult, UpdaterChannel/State) are now defined both in src/types/ipc.ts and src/main/updater.ts; consider consolidating these into a single shared definition to avoid divergence between main and renderer contracts.
- There is repeated error/boolean/env parsing logic (e.g., TRUE_VALUES + parseBooleanEnv, getErrorMessage) across main/index.ts, src/main/updater.ts, and src/main/feature-flags.ts; extracting these into a small shared utility would reduce duplication and keep behavior consistent.
- The assets protocol fallback (createForbiddenAssetResponse) returns a plain object cast as Response when Response is not defined, but protocol.handle expects a real Fetch Response; it might be more robust to ensure a proper Response implementation is always available in the main process (or to avoid the casted object path entirely).

## Individual Comments

### Comment 1
<location> `src/main/index.ts:523` </location>
<code_context>

-const getErrorMessage = (error: unknown): string =>
-  error instanceof Error ? error.message : String(error);
+function getErrorMessage(error: unknown): string {
+  if (error instanceof Error) {
+    return error.message;
</code_context>

<issue_to_address>
**suggestion:** Avoid duplicating `getErrorMessage` logic between main/index and main/updater.

A near-identical `getErrorMessage` now exists here and in `src/main/updater.ts`, which increases the chance they diverge over time. Please extract this into a shared utility and use it from both places to keep error handling consistent and easier to maintain.

Suggested implementation:

```typescript

```

To fully implement the suggestion and avoid duplication:

1. Create a shared utility in `src/main/errors.ts`:
   ```ts
   // src/main/errors.ts
   export function getErrorMessage(error: unknown): string {
     if (error instanceof Error) {
       return error.message;
     }

     if (typeof error === 'object' && error !== null) {
       try {
         return JSON.stringify(error);
       } catch {
         return '[unserializable error object]';
       }
     }

     return String(error);
   }
   ```

2. In `src/main/index.ts`, add an import near the top of the file (alongside other imports):
   ```ts
   import { getErrorMessage } from './errors';
   ```

3. In `src/main/updater.ts`, remove its local `getErrorMessage` implementation (similar to the change above) and import the shared utility:
   ```ts
   import { getErrorMessage } from './errors';
   ```

These changes centralize the error handling logic, ensuring `index.ts` and `updater.ts` stay consistent and easier to maintain.
</issue_to_address>

### Comment 2
<location> `src/main/updater.ts:3-12` </location>
<code_context>
+import type { AppUpdater } from 'electron-updater';
+
+export type UpdaterChannel = 'alpha' | 'stable';
+export type UpdaterState = 'disabled' | 'up-to-date' | 'update-available' | 'error';
+
+export interface UpdaterStatus {
+  enabled: boolean;
+  platformSupported: boolean;
+  channel: UpdaterChannel;
+  allowPrerelease: boolean;
+  currentVersion: string;
+  owner: string;
+  repo: string;
+  reason?: string;
+}
+
+export interface UpdateCheckResult extends UpdaterStatus {
+  state: UpdaterState;
+  updateAvailable: boolean;
</code_context>

<issue_to_address>
**suggestion:** Consolidate updater-related types to avoid drift between main and IPC typings.

`UpdaterChannel`, `UpdaterState`, `UpdaterStatus`, and `UpdateCheckResult` are defined both here and in `src/types/ipc.ts`. If they diverge, main and renderer could disagree on updater response shapes. Please move these to a single shared location (e.g., the IPC types file) and import them where needed to keep one source of truth.

Suggested implementation:

```typescript
import type { AppUpdater } from 'electron-updater';
import type {
  UpdaterChannel,
  UpdaterState,
  UpdaterStatus,
  UpdateCheckResult,
} from '../types/ipc';

export interface UpdaterRuntimeOptions extends UpdaterStatus {

```

To fully consolidate the updater-related types, you should also:

1. Ensure `src/types/ipc.ts` exports **exactly** these four types with matching shapes:
   - `UpdaterChannel`
   - `UpdaterState`
   - `UpdaterStatus`
   - `UpdateCheckResult` (including any fields such as `latestVersion`, `releaseName`, `errorMessage` that are expected by main and renderer).
2. If the relative path from `src/main/updater.ts` to the IPC types differs in your repo structure, adjust the import path (`'../types/ipc'`) accordingly.
3. If `UpdaterRuntimeOptions` or `UpdaterFlagOverrides` are also used across the IPC boundary, consider moving them to `src/types/ipc.ts` as well and importing them here in the same way.
</issue_to_address>

### Comment 3
<location> `src/renderer/context/DarkModeContext.tsx:50-57` </location>
<code_context>
     }

-    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+    const mediaQuery = appWindow.matchMedia('(prefers-color-scheme: dark)');
     const handleChange = (event: MediaQueryListEvent) => {
       if (localStorage.getItem('darkMode') === null) {
         setDarkMode(event.matches);
       }
     };

-    if (typeof mediaQuery.addEventListener === 'function') {
-      mediaQuery.addEventListener('change', handleChange);
-      return () => mediaQuery.removeEventListener('change', handleChange);
-    }
</code_context>

<issue_to_address>
**issue (bug_risk):** Dropping the `addListener` fallback may break dark-mode syncing on older environments.

The previous code guarded against environments where `MediaQueryList.addEventListener` is missing by falling back to `addListener`. The new version assumes `addEventListener` is always present, which can cause runtime errors in older Electron or some test environments. If those environments are still in scope, please add a feature check and fall back to `addListener` when `addEventListener` is unavailable.
</issue_to_address>

### Comment 4
<location> `tests/integration/main-process/handlers.test.ts:232` </location>
<code_context>
+      expect(mockNetFetch).not.toHaveBeenCalled();
     });

     test('should resolve valid assets protocol requests', async () => {
</code_context>

<issue_to_address>
**suggestion (testing):** Extend assets protocol tests to cover URL host/path combinations and encoded segments

Since the handler now splits and decodes hostname and pathname, the current test with only `assets://icon.png` doesn’t exercise the full behavior. Please add cases like `assets://public/icon.png`, `assets://host/dir/file.png`, and a path with URL-encoded characters (e.g., spaces or encoded `..`) to verify the join/decode logic and that the root constraint still holds. This will help detect regressions in the host vs path parsing logic.

Suggested implementation:

```typescript
    test('should resolve valid assets protocol requests', async () => {
      const handler = mockProtocolHandlers['assets'];
      expect(handler).toBeDefined();

      // Basic asset path without host
      const iconResponse = await handler({ url: 'assets://icon.png' });
      expect(iconResponse).toBeDefined();

      // Asset path including a host segment
      const hostIconResponse = await handler({ url: 'assets://public/icon.png' });
      expect(hostIconResponse).toBeDefined();

      // Nested host and directory segments
      const nestedResponse = await handler({ url: 'assets://host/dir/file.png' });
      expect(nestedResponse).toBeDefined();

      // URL-encoded path segments should be decoded but still constrained to the assets root
      const encodedSpaceResponse = await handler({
        url: 'assets://host/dir/space%20file.png',
      });
      expect(encodedSpaceResponse).toBeDefined();

      // Encoded traversal should still be rejected by the root constraint
      const encodedTraversalResponse = await handler({
        url: 'assets://host/dir/%2e%2e/%2e%2e/secret.txt',
      });
      expect(encodedTraversalResponse).toBeDefined();
      expect(encodedTraversalResponse.status).toBe(403);

      expect(mockNetFetch).not.toHaveBeenCalled();

```

1. If the existing `should resolve valid assets protocol requests` test already contains assertions for `assets://icon.png`, merge those with the new cases to avoid duplicate calls/assertions.
2. Adjust the expectations on `status` (`200`, `404`, etc.) for the valid URLs (`iconResponse`, `hostIconResponse`, `nestedResponse`, `encodedSpaceResponse`) to match the actual behavior of your assets handler (if it returns specific status codes instead of just being defined).
3. Ensure `mockNetFetch` is in scope for this test and correctly reset/mocked between tests so that the final `expect(mockNetFetch).not.toHaveBeenCalled();` remains accurate.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/main/index.ts Outdated
Comment thread src/main/updater.ts Outdated
Comment thread src/renderer/context/DarkModeContext.tsx Outdated
Comment thread tests/integration/main-process/handlers.test.ts

@gemini-code-assist gemini-code-assist 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.

Code Review

This is an excellent pull request that introduces a well-structured, feature-flag-driven auto-updater and delivers a significant number of quality improvements across the codebase. The refactoring in the main and renderer processes greatly enhances maintainability, readability, and security. I particularly appreciate the more robust protocol handling, the improved secret scanning logic, and the consistent use of modern language features. The addition of new tests for the updater logic is also a great touch. I have one minor suggestion regarding a transitive dependency.

Comment thread package.json

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc80543c6b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/updater.ts Outdated
Comment thread src/utils/secret-scanner.ts Outdated
@qodo-free-for-open-source-projects

This comment has been minimized.

Comment thread src/main/feature-flags.ts
Comment thread src/main/index.ts Outdated

@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: 3

🤖 Fix all issues with AI agents
In `@src/main/updater.ts`:
- Around line 120-142: Extract the getErrorMessage function into a shared
utility module: create a new module that exports getErrorMessage (e.g., export
const getErrorMessage = ...) and move the exact implementation there, then
replace the local definitions in both updater.ts and index.ts with an import of
getErrorMessage from the new module; ensure the function signature and behavior
are unchanged and update the import statements in the files that previously
defined getErrorMessage (referencing the getErrorMessage symbol to locate the
duplicates) so there are no remaining duplicate definitions.

In `@src/utils/content-processor.ts`:
- Line 50: The use of String.prototype.replaceAll on path.extname(filePath)
(assigned to fileType) is incompatible with the current TS lib target; either
update tsconfig.json to include ES2021 (e.g., add "ES2021" to "lib") so
replaceAll is supported, or change the expression that computes fileType (the
const fileType = path.extname(filePath)... usage) to use a regex-based replace
(String.prototype.replace with a global regex to remove dots) instead; apply the
same change for the other occurrences mentioned (the other
path.extname(...).replaceAll calls).

In `@src/utils/export-format.ts`:
- Around line 10-21: The build fails because string.replaceAll is used in
sanitizeXmlContent, escapeXmlAttribute, and wrapXmlCdata but the TS lib doesn't
include ES2021.String; fix by either adding "ES2021" or "ES2021.String" to the
tsconfig lib array so replaceAll is allowed, or replace every .replaceAll(...)
call in sanitizeXmlContent, escapeXmlAttribute, and wrapXmlCdata with
.replace(...) using equivalent global RegExp (e.g. /.*/g) so TypeScript builds
without changing tsconfig.
🧹 Nitpick comments (12)
scripts/sonar-scan.js (1)

278-296: Coverage-existence check runs even when generation is skipped.

Lines 293-296 unconditionally require coverage/lcov.info to exist, even when SONAR_GENERATE_COVERAGE=false. This means someone who disables generation but doesn't have coverage at that exact path will get a hard failure. If that's intentional (always require coverage for Sonar), this is fine. If you want to allow scans without coverage, gate the check:

Proposed fix: gate the existence check
-if (!fs.existsSync(coveragePath)) {
-  console.error(`Error: Coverage report not found at ${coveragePath}`);
-  process.exit(1);
-}
+if (shouldGenerateCoverage && !fs.existsSync(coveragePath)) {
+  console.error(`Error: Coverage report not found at ${coveragePath}`);
+  process.exit(1);
+}
src/utils/secret-scanner.ts (1)

5-10: Consider enforcing that at least one detection mechanism is provided.

Both pattern and matches are optional. A SecretRule with neither field would silently never match. While the current rule set is correct, a discriminated union or runtime assertion would prevent future misuse.

♻️ Type-level enforcement with a union
-type SecretRule = {
-  id: string;
-  description: string;
-  pattern?: RegExp;
-  matches?: (content: string) => boolean;
-};
+type SecretRule = {
+  id: string;
+  description: string;
+} & (
+  | { pattern: RegExp; matches?: never }
+  | { matches: (content: string) => boolean; pattern?: never }
+  | { pattern: RegExp; matches: (content: string) => boolean }
+);
src/renderer/components/ConfigTab.tsx (1)

254-260: appWindow in the dependency array is a stable reference — consider removing it.

appWindow is reassigned to globalThis on every render, but globalThis itself is a singleton and never changes identity. Including it in the dependency array is harmless but misleading, as it implies the effect should re-run when the window reference changes (which it won't). The linter may have requested it; if so, this is fine as-is.

src/main/preload.ts (1)

12-13: Consolidate duplicate interface definitions to avoid potential drift.

UpdaterStatus and UpdateCheckResult interfaces are defined identically in both src/types/ipc.ts (lines 85-101) and src/main/updater.ts (lines 6-23). Since src/main/preload.ts imports from src/types/ipc.ts, update src/main/updater.ts to import and re-export these interfaces from src/types/ipc.ts instead of maintaining separate copies.

tests/integration/main-process/handlers.test.ts (1)

314-366: Platform-dependent test branches may pass vacuously on some CI runners.

The if (platformSupported) branching at line 340 means on Linux CI (the most common), only the else (disabled) path is exercised. The "supported platform" path with setFeedURL/checkForUpdates assertions is never reached. Consider adding a dedicated test that mocks process.platform to 'win32' or 'darwin' so the supported-platform branch is covered regardless of the runner OS.

src/renderer/components/SourceTab.tsx (1)

191-253: renderProcessButtonContent is re-created on every render.

This works correctly since it's only called locally. If you wanted to optimize, it could be wrapped in useCallback or extracted outside the component as a pure function taking isAnalyzing/isCalculating as arguments — but this is purely optional given it's not passed as a prop to child components.

src/renderer/components/App.tsx (1)

188-188: Assigning globals during render is a side effect — move to useEffect.

appWindow.switchToTab (line 188) and appWindow.refreshDirectoryTree (line 211) are set during render. In React Strict Mode the render function runs twice, and more importantly, this is an impure side effect during the render phase. Wrap these assignments in a useEffect so they are updated after commit:

♻️ Suggested approach
+  useEffect(() => {
+    appWindow.switchToTab = handleTabChange;
+    appWindow.refreshDirectoryTree = refreshDirectoryTree;
+  });
-  appWindow.switchToTab = handleTabChange;
   ...
-  appWindow.refreshDirectoryTree = refreshDirectoryTree;

Using useEffect without a deps array ensures the globals are always current after each render.

Also applies to: 210-211

src/main/feature-flags.ts (2)

4-4: TRUE_VALUES is duplicated in src/main/updater.ts (line 36).

Both files define the same new Set(['1', 'true', 'yes', 'on']). Extract it to a shared utility to keep a single source of truth.


51-60: Env vars are read here and again in resolveUpdaterRuntimeOptions — the double read is redundant.

readUpdaterFlagOverridesFromEnv reads UPDATER_ENABLED, UPDATER_CHECK_ON_START, UPDATER_GH_OWNER, UPDATER_GH_REPO. Then resolveUpdaterRuntimeOptions (updater.ts, lines 75–82) reads the same env vars as a fallback behind flagOverrides.*. Since the flag overrides passed to resolveUpdaterRuntimeOptions already incorporate the env values, the fallback in resolveUpdaterRuntimeOptions is effectively dead code. Consider having resolveUpdaterRuntimeOptions rely solely on flagOverrides when they are provided, rather than re-reading the environment.

src/main/index.ts (2)

102-114: Consider adding a .catch to the fire-and-forget promise chain.

While checkForUpdates internally catches errors, the .then() callback itself could theoretically throw, producing an unhandled rejection. A trailing .catch is cheap insurance:

♻️ Proposed fix
   void updaterService.checkForUpdates().then((result) => {
     if (result.state === 'error') {
       console.warn(`Startup update check failed: ${result.errorMessage}`);
     } else if (result.state === 'update-available') {
       console.info(`Update available: ${result.latestVersion ?? 'unknown version'}`);
     }
-  });
+  }).catch((err) => {
+    console.warn('Unexpected error during startup update check:', err);
+  });

661-661: Shadowed tokenCounter — consider reusing the module-level singleton.

Line 661 creates a new TokenCounter() that shadows the module-level instance at line 38. If there's no state isolation concern, reusing the singleton avoids unnecessary allocation.

src/types/ipc.ts (1)

3-4: Duplicate type definitions with src/main/updater.ts.

UpdaterChannel, UpdaterState, UpdaterStatus, and UpdateCheckResult are defined identically in both src/types/ipc.ts and src/main/updater.ts. Consolidate these types to a single source — either ipc.ts exports them and updater.ts imports them, or vice versa.

Comment thread src/main/updater.ts Outdated
Comment thread src/utils/content-processor.ts
Comment thread src/utils/export-format.ts

@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

🤖 Fix all issues with AI agents
In `@src/main/feature-flags.ts`:
- Around line 212-214: getValueOrUndefined currently returns an empty string for
unset string flags (from client.getStringDetails), losing the "not configured"
semantics; update getValueOrUndefined (or add a separate helper like
getStringValueOrUndefined) to treat an empty string as undefined by returning
undefined when typeof details.value === 'string' && details.value === '' (in
addition to existing details.reason === 'ERROR' check), and update all call
sites that handle string flags (e.g., owner/repo resolution logic that builds
UpdaterFlagOverrides) to use the new behavior so unset string flags become
undefined.
🧹 Nitpick comments (5)
src/renderer/context/DarkModeContext.tsx (1)

13-13: The appWindow alias is a pragmatic test-friendliness trick, but consider documenting intent.

Casting globalThis to Window & typeof globalThis works in the renderer but silently suppresses type errors if this module is ever imported in a non-browser context (e.g., SSR or a Node-based test runner without jsdom). A one-line comment explaining why the alias exists (e.g., to decouple from window for testability or Electron compatibility) would help future readers.

src/utils/secret-scanner.ts (1)

5-10: SecretRule allows both pattern and matches to be undefined, producing a silent no-op rule.

Not a runtime bug (the scan logic safely evaluates to false), but the type doesn't enforce that at least one detection mechanism is provided. Consider a discriminated union or a runtime assertion if you want to prevent accidentally adding a rule with neither field.

src/main/feature-flags.ts (1)

39-48: parseBoolean returns false for any unrecognized string (e.g., "false", "no").

This is intentional one-way truthy parsing, but it means there's no way to distinguish "explicitly set to false" from "not set" when the input is a string like "false". Since the return type is boolean | undefined, and env vars like UPDATER_ENABLED=false would return false (the boolean), this seems acceptable for the env-var use case — just calling it out for awareness.

src/main/index.ts (1)

550-553: treeView parameter type should include null to match ProcessRepositoryOptions.

ProcessRepositoryOptions.treeView is typed as string | null (in src/types/ipc.ts), but buildRepositoryHeader declares it as string | undefined. The || fallback on line 561 handles both at runtime, but this is a type mismatch that would surface under strictNullChecks.

Proposed fix
 const buildRepositoryHeader = (
   processingOptions: RepositoryProcessingOptions,
-  treeView: string | undefined,
+  treeView: string | null | undefined,
   filesInfo: FileInfo[]
 ): string => {
src/main/updater.ts (1)

14-21: parseBooleanEnv silently returns false for unrecognized non-empty values.

Any non-empty string not in TRUE_VALUES (e.g., "banana", "2") returns false rather than undefined. This collapses "explicitly false" and "unrecognized/misconfigured" into the same result, which could mask a typo in an env var (e.g., UPDATER_ENABLED=ture). Consider returning undefined for values that aren't recognized as either true or false.

Proposed fix
+const FALSE_VALUES = new Set(['0', 'false', 'no', 'off']);
+
 export const parseBooleanEnv = (value: string | undefined): boolean | undefined => {
   if (!value) {
     return undefined;
   }
-  return TRUE_VALUES.has(value.trim().toLowerCase());
+  const normalized = value.trim().toLowerCase();
+  if (TRUE_VALUES.has(normalized)) return true;
+  if (FALSE_VALUES.has(normalized)) return false;
+  return undefined;
 };

Comment thread src/main/feature-flags.ts
Comment on lines +212 to +214
const getValueOrUndefined = <T>(details: { value: T; reason?: string }): T | undefined => {
return details.reason === 'ERROR' ? undefined : details.value;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unset string flags return '' instead of undefined, losing the "not configured" signal.

When a string flag (owner/repo) isn't in the configuration, client.getStringDetails(key, '') returns '' with a non-'ERROR' reason. getValueOrUndefined then returns '' rather than undefined. This means the returned UpdaterFlagOverrides will have owner: '' / repo: '' for unconfigured flags, which differs from the undefined semantics used everywhere else in this module.

Consider checking for empty string in getValueOrUndefined for string details, or using a separate helper for string flags:

Suggested approach
-const getValueOrUndefined = <T>(details: { value: T; reason?: string }): T | undefined => {
-  return details.reason === 'ERROR' ? undefined : details.value;
-};
+const getValueOrUndefined = <T>(details: { value: T; reason?: string }): T | undefined => {
+  if (details.reason === 'ERROR') {
+    return undefined;
+  }
+  if (typeof details.value === 'string' && details.value === '') {
+    return undefined;
+  }
+  return details.value;
+};

Also applies to: 241-248

🤖 Prompt for AI Agents
In `@src/main/feature-flags.ts` around lines 212 - 214, getValueOrUndefined
currently returns an empty string for unset string flags (from
client.getStringDetails), losing the "not configured" semantics; update
getValueOrUndefined (or add a separate helper like getStringValueOrUndefined) to
treat an empty string as undefined by returning undefined when typeof
details.value === 'string' && details.value === '' (in addition to existing
details.reason === 'ERROR' check), and update all call sites that handle string
flags (e.g., owner/repo resolution logic that builds UpdaterFlagOverrides) to
use the new behavior so unset string flags become undefined.

@sonarqubecloud

This comment has been minimized.

@Mehdi-Bl Mehdi-Bl merged commit ccc64f8 into main Feb 10, 2026
15 checks passed
@Mehdi-Bl Mehdi-Bl deleted the feat/updater-planning-squash branch February 10, 2026 05:48
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.

1 participant