Skip to content

Bug: imsg chats/history can hang when Contacts auth is not determined #135

@cemendes

Description

@cemendes

Bug: imsg chats / imsg history can hang on macOS when Contacts auth is .notDetermined

Summary

On macOS 26.4.1, imsg chats and imsg history can hang indefinitely in CLI usage when Contacts permission has not yet been decided (CNContactStore.authorizationStatus(for: .contacts) == .notDetermined).

This appears to be caused by ContactResolver.create() awaiting requestAccess(for: .contacts) from a CLI path. In this environment, the callback never resolves promptly, so the command appears to hang.

What works

  • imsg group --chat-id 1090 --json returns immediately
  • direct Hermes SQLite fallback works
  • Messages DB is readable enough for non-Contacts-dependent flows

What hangs

  • imsg chats --limit 1 --json
  • imsg chats --limit 1
  • imsg history --chat-id 1090 --limit 1 --json

Evidence

Direct Swift probe:

swift -e 'import Contacts; print(CNContactStore.authorizationStatus(for: .contacts).rawValue)'

returns:

0

(.notDetermined)

And:

swift -e '
import Contacts
import Foundation
let s = CNContactStore()
print("before", CNContactStore.authorizationStatus(for: .contacts).rawValue)
let sem = DispatchSemaphore(value: 0)
s.requestAccess(for: .contacts) { granted, err in
  print("granted", granted)
  print(err?.localizedDescription ?? "nil")
  sem.signal()
}
let r = sem.wait(timeout: .now() + 5)
print("wait", r == .success ? "done" : "timeout")
'

outputs:

before 0
wait timeout

So the permission request path itself is the hang.

Likely root cause

ChatsCommand.run() and HistoryCommand.run() eagerly call:

await ContactResolver.create()

And ContactResolver.create() currently does:

case .notDetermined:
  let granted = await requestAccess(store: store)
  return granted ? load(...) : NoOpContactResolver(...)

That blocks core read commands on a permission flow that may not complete in CLI/headless contexts.

Proposed fix

Fail open for .notDetermined:

case .notDetermined:
  return NoOpContactResolver(contactsUnavailable: true)

This preserves core chats / history behavior and only disables optional contact-name enrichment.

Better long-term design

Make contact resolution opt-in (for example --resolve-contacts) instead of automatic for all chats / history calls.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal priority bug or improvement with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:crash-loopThis issue is about crashes, hangs, restart loops, or process-level availability.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions