Skip to content

App crashes with duplicate repository keys (Dictionary and NSMenu) #31

@ggfevans

Description

@ggfevans

Description

RepoBar crashes when repositories with duplicate keys are present in the data pipeline. This manifests as two different crash types:

Crash 1: Dictionary Assertion Failure

Crashes immediately on launch with an assertion failure in Dictionary(uniqueKeysWithValues:).

Exception Type:    EXC_BREAKPOINT (SIGTRAP)
Thread 0 Crashed - _assertionFailure(_:_:file:line:flags:)

Stack trace:
0   libswiftCore.dylib    _assertionFailure
1   RepoBar               specialized _NativeDictionary.merge<A>(_:isUnique:uniquingKeysWith:)
2   RepoBar               specialized AppState.mergeHydrated(_:into:)
3   RepoBar               AppState.refresh()
4   RepoBar               closure #1 in AppState.requestRefresh(cancelInFlight:)

Crash 2: NSMenu Item Already Attached

Crashes when opening the menu with an NSMenu assertion failure.

*** Assertion failure in <private>, NSMenu.m:1360
NSMenu: Exception raised while opening the menu: Item to be inserted into menu already is in another menu
*** Inconsistent state. A menu item's height should never be 0.

This occurs because the same cached NSMenuItem is returned for duplicate repos and added to the menu twice.

Root Cause

Both crashes share the same root cause: duplicate repositories appearing in the data pipeline. This can occur when:

  1. Pinned repositories have case variations of the same repo name
  2. The GitHub API returns the same repository via different code paths during hydration
  3. Repository list contains duplicates before filtering

Affected Code

Dictionary crash - AppState+Refresh.swift:

private func mergeHydrated(_ detailed: [Repository], into repos: [Repository]) -> [Repository] {
    let lookup = Dictionary(uniqueKeysWithValues: detailed.map { ($0.fullName, $0) })
    // crashes when duplicate fullName keys exist
}

Menu crash - StatusBarMenuBuilder.swift:

for (index, repo) in repos.enumerated() {
    let item = self.repoMenuItem(for: repo, isPinned: isPinned)
    items.append(item)  // same cached item added twice if duplicates exist
}

Environment

  • RepoBar version: 0.2.0
  • macOS: 26.2 (25C56)
  • Hardware: Mac16,12 (ARM64)
  • User has pinned repositories configured

Proposed Fix

Dictionary fix

Replace Dictionary(uniqueKeysWithValues:) with Dictionary(_:uniquingKeysWith:) which gracefully handles duplicate keys:

let lookup = Dictionary(detailed.map { ($0.fullName, $0) }, uniquingKeysWith: { first, _ in first })

Menu fix

Deduplicate repos by id before building menu items:

var usedRepoKeys: Set<String> = []
let uniqueRepos = repos.filter { repo in
    if usedRepoKeys.contains(repo.id) { return false }
    usedRepoKeys.insert(repo.id)
    return true
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions