# NousResearch/hermes-agent · Model visibility dialog hide-all preservation bug fixes

## Open now

- Nothing open on GitHub.

These issues and pull requests concern a bug in the desktop model visibility dialog where toggling off the last model in a provider group incorrectly re-enables all models, along with multiple fix attempts to preserve explicit hide-all states for providers.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #43496. Verify it addresses your case.
- **Recommended:** read PR #43496 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-43496`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #43485: \[Bug\] Model visibility: toggling off last model in a provider group re-enables all models

## Prior & competing PRs

- **#43496: fix(desktop): preserve explicit hide-all choice in model visibility dialog**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `apps/desktop/src/components/model-visibility-dialog.tsx`, `apps/desktop/src/store/model-visibility.test.ts`, `apps/desktop/src/store/model-visibility.ts`
  - fix hunks:
  - `apps/desktop/src/components/model-visibility-dialog.tsx`

    ```diff
    @@ -14,6 +14,8 @@ import {
       $visibleModels,
       collapseModelFamilies,
       effectiveVisibleKeys,
    +  emptyProviderSentinelKey,
    +  isProviderSentinel,
       modelVisibilityKey,
       setVisibleModels
     } from '@/store/model-visibility'
    ```

    ```diff
    @@ -61,10 +63,21 @@ export function ModelVisibilityDialog({
       const toggle = (provider: ModelOptionProvider, model: string) => {
         const next = new Set(effectiveVisibleKeys($visibleModels.get(), providers))
         const key = modelVisibilityKey(provider.slug, model)
    +    const sentinel = emptyProviderSentinelKey(provider.slug)
     
         if (next.has(key)) {
           next.delete(key)
    +
    +      // Check if this was the last real model for this provider.
    +      const remainingForProvider = [...next].some(
    +        k => k.startsWith(`${provider.slug}::`) && !isProviderSentinel(k)
    +      )
    +
    +      if (!remainingForProvider) {
    +        next.add(sentinel)
    +      }
         } else {
    +      next.delete(sentinel)
           next.add(key)
         }
     
    ```
  - `apps/desktop/src/store/model-visibility.test.ts`

    ```diff
    @@ -2,7 +2,12 @@ import { describe, expect, it } from 'vitest'
     
     import type { ModelOptionProvider } from '@/types/hermes'
     
    -import { effectiveVisibleKeys, modelVisibilityKey } from './model-visibility'
    +import {
    +  effectiveVisibleKeys,
    +  emptyProviderSentinelKey,
    +  isProviderSentinel,
    +  modelVisibilityKey
    +} from './model-visibility'
     
     const provider = (slug: string, models: string[]): ModelOptionProvider => ({
       models,
    ```

    ```diff
    @@ -34,4 +39,48 @@ describe('model visibility', () => {
         expect(visible.has(modelVisibilityKey('local-ollama', 'qwen3:latest'))).toBe(true)
         expect(visible.has(modelVisibilityKey('local-ollama', 'llama3.2:latest'))).toBe(false)
       })
    +
    +  it('preserves hidden-provider sentinel without re-adding defaults', () => {
    +    // User explicitly hid all models for "nous" — sentinel marks this choice.
    +    const stored = new Set([emptyProviderSentinelKey('nous')])
    +
    +    const visible = effectiveVisibleKeys(stored, [
    +      provider('nous', ['hermes-3-llama-3.1-70b', 'hermes-3-llama-3.1-8b']),
    +      provider('ollama', ['qwen3:latest'])
    +    ])
    +
    +    expect(visible.has(modelVisibilityKey('nous', 'hermes-3-llama-3.1-70b'))).toBe(false)
    +    expect(visible.has(modelVisibilityKey('nous', 'hermes-3-llama-3.1-8b'))).toBe(false)
    +    // Sentinel itself is stripped from the result.
    +    expect(visible.has(emptyProviderSentinelKey('nous'))).toBe(false)
    +    // Other providers still get defaults.
    +    expect(visible.has(modelVisibilityKey('ollama', 'qwen3:latest'))).toBe(true)
    +  })
    +
    +  it('restores model when toggling on after hiding all', () => {
    +    // Simulates: user hid all "nous" models, then toggles one back on.
    +    const stored = new Set([
    +      emptyProviderSentinelKey('nous'),
    +      modelVisibilityKey('ollama', 'qwen3:latest')
    +    ])
    +
    +    // After toggle: sentinel removed, one model added.
    +    const afterToggle = new Set(stored)
    +    afterToggle.delete(emptyProviderSentinelKey('nous'))
    +    afterToggle.add(modelVisibilityKey('nous', 'hermes-3-llama-3.1-70b'))
    +
    +    const visible = effectiveVisibleKeys(afterToggle, [
    +      provider('nous', ['hermes-3-llama-3.1-70b', 'hermes-3-llama-3.1-8b']),
    +      provider('ollama', ['qwen3:latest'])
    +    ])
    +
    +    expect(visible.has(modelVisibilityKey('nous', 'hermes-3-llama-3.1-70b'))).toBe(true)
    +    expect(visible.has(modelVisibilityKey('nous', 'hermes-3-llama-3.1-8b'))).toBe(false)
    +  })
    +
    +  it('sentinel key helper produces correct format', () => {
    +    expect(emptyProviderSentinelKey('openai')).toBe('openai::')
    +    expect(isProviderSentinel('openai::')).toBe(true)
    +    expect(isProviderSentinel('openai::gpt-4o')).toBe(false)
    +  })
     })
    ```
  - `apps/desktop/src/store/model-visibility.ts`

    ```diff
    @@ -13,6 +13,19 @@ export const DEFAULT_VISIBLE_PER_PROVIDER = 50
      *  that contain a single colon, e.g. `model:tag`). */
     export const modelVisibilityKey = (provider: string, model: string): string => `${provider}::${model}`
     
    +/** Sentinel key suffix stored when the user explicitly hides ALL models for a
    + *  provider.  Distinguishes "user hid everything" from "never customized" so
    + *  `effectiveVisibleKeys` does not re-add defaults for that provider. */
    +export const EMPTY_PROVIDER_SENTINEL = ''
    +
    +/** Build the sentinel key for a provider whose last model was toggled off. */
    +export const emptyProviderSentinelKey = (provider: string): string =>
    +  modelVisibilityKey(provider, EMPTY_PROVIDER_SENTINEL)
    +
    +/** Check whether a stored key is a provider-hidden sentinel. */
    +export const isProviderSentinel = (key: string): boolean =>
    +  key.endsWith('::')
    +
     /** A model and its optional `…-fast` sibling, collapsed into one logical row.
      *  `id` is the canonical (base) model; `fastId` is the fast variant if present. */
     export interface ModelFamily {
    ```

    ```diff
    @@ -116,9 +129,12 @@ export function effectiveVisibleKeys(
     
       for (const provider of providers) {
         const providerPrefix = `${provider.slug}::`
    -    const hasStoredProvider = [...stored].some(key => key.startsWith(providerPrefix))
    +    const hasStoredProvider = [...stored].some(
    +      key => key.startsWith(providerPrefix) && !isProviderSentinel(key)
    +    )
    +    const hasSentinel = stored.has(emptyProviderSentinelKey(provider.slug))
     
    -    if (hasStoredProvider) {
    +    if (hasStoredProvider || hasSentinel) {
           continue
         }
     
    ```

    ```diff
    @@ -129,5 +145,12 @@ export function effectiveVisibleKeys(
         }
       }
     
    +  // Strip sentinel keys — they are bookkeeping, not real visibility entries.
    +  for (const key of [...next]) {
    +    if (isProviderSentinel(key)) {
    +      next.delete(key)
    +    }
    +  }
    +
       return next
     }
    ```
- **#43542: fix(desktop): preserve empty model visibility providers**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#43496`, `#43485`

## Fix status

- Merged to main in PR #43496 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-11`.
