Problem
Package.swift is swift-tools-version: 5.10 with no Swift 6 language mode. The CGEvent-tap hot path reads module-level var globals — shortcutList, exclusionAppsDict, isRecordingShortcut, keyMappingList, activeAppsList, exclusionAppsList.
Today there is no live data race: the tap's run-loop source is added to the main run loop (CFRunLoopGetMain()), and AppSettings (the writer) is @MainActor — so every reader and writer is on the main thread. But that contract is invisible to the compiler. Under Swift 6 strict concurrency these globals are hard errors (non-Sendable mutable global state), and the safety relies entirely on a convention that is easy to break (e.g. moving the tap to a background run loop).
Suggested approach
- Wrap the shared caches in a
@MainActor holder type (or a small lock-guarded struct).
- Enable Swift 6 language mode (
swiftLanguageModes: [.v6] / per-target setting).
- Let the compiler enforce the main-thread contract that is currently only documented in
CLAUDE.md.
Forward-looking; related to closed #73 ("crash risk under Swift 6").
Problem
Package.swiftisswift-tools-version: 5.10with no Swift 6 language mode. The CGEvent-tap hot path reads module-levelvarglobals —shortcutList,exclusionAppsDict,isRecordingShortcut,keyMappingList,activeAppsList,exclusionAppsList.Today there is no live data race: the tap's run-loop source is added to the main run loop (
CFRunLoopGetMain()), andAppSettings(the writer) is@MainActor— so every reader and writer is on the main thread. But that contract is invisible to the compiler. Under Swift 6 strict concurrency these globals are hard errors (non-Sendablemutable global state), and the safety relies entirely on a convention that is easy to break (e.g. moving the tap to a background run loop).Suggested approach
@MainActorholder type (or a small lock-guarded struct).swiftLanguageModes: [.v6]/ per-target setting).CLAUDE.md.Forward-looking; related to closed #73 ("crash risk under Swift 6").