Skip to content

Prevent preference defaults startup crash - #1295

Merged
malpern merged 1 commit into
masterfrom
codex/preferences-defaults-fallback
Sep 13, 2026
Merged

malpern merged 1 commit into
masterfrom
codex/preferences-defaults-fallback

Conversation

@malpern

@malpern malpern commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • fall back to standard defaults if the named preference suite is unavailable at launch
  • remove the startup forced unwrap that caused a launchd crash loop
  • add a regression test for the nil-suite path

Validation

  • 🧪 Running tests via swift test with safety guards...
    ⏱️ Timeout: 240s
    🛣️ Test lane: full
    🔎 Test filter: PreferencesServiceTests
    🏗️ Test prebuild: 1 | disable XCTest: 0 | reset module cache: 0
    🧪 SWIFT_TEST=1 | SKIP_EVENT_TAP_TESTS=1
    🪵 KEYPATH_LOG_LEVEL=3 | KEYPATH_TEST_VERBOSE_LOGS=0
    🧹 Refreshing generated /Users/malpern/local-code/KeyPath/.worktrees/preferences-defaults-fallback/.build/debug symlink
    ♻️ Reusing generated module cache: /Users/malpern/local-code/KeyPath/.worktrees/preferences-defaults-fallback/.build/ModuleCache.noindex
    🎯 Swift test args: --filter PreferencesServiceTests
    📦 Scratch: /Users/malpern/local-code/KeyPath/.worktrees/preferences-defaults-fallback/.build | HOME=/private/var/folders/0s/42v7yl697hn0l1cdnx0vrvyr0000gn/T/tmp.wA8mCB6K4a
    🗂️ Module cache: /Users/malpern/local-code/KeyPath/.worktrees/preferences-defaults-fallback/.build/ModuleCache.noindex
    🔨 Building tests...
    [0/1] Planning build
    [1/1] Compiling plugin CompileKeyboardStageMetal
    [2/2] Compiling plugin GenerateManual
    [3/3] Compiling plugin GenerateDoccReference
    Building for debugging...
    [3/18] Write swift-version--112E4EF74740DA65.txt
    Build complete! (3.12s)
    🚀 Launching swift test...
    [0/1] Planning build
    Test Suite 'Selected tests' started at 2026-09-13 09:37:47.157.
    Test Suite 'KeyPathPackageTests.xctest' started at 2026-09-13 09:37:47.158.
    Test Suite 'PreferencesServiceTests' started at 2026-09-13 09:37:47.158.
    Test Case '-[KeyPathTests.PreferencesServiceTests testCanonicalDefaultsFallsBackToStandardWhenSuiteIsUnavailable]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testCanonicalDefaultsFallsBackToStandardWhenSuiteIsUnavailable]' passed (0.002 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testCommunicationConfigDescription_ContainsTCPAndPort]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testCommunicationConfigDescription_ContainsTCPAndPort]' passed (0.001 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testCommunicationProtocol_TCPHasDisplayInfo]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testCommunicationProtocol_TCPHasDisplayInfo]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDDisplayMode_AllCasesHaveDisplayNames]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDDisplayMode_AllCasesHaveDisplayNames]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayMs_CustomPresetUsesCustomValue]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayMs_CustomPresetUsesCustomValue]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayMs_LongPreset]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayMs_LongPreset]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayMs_MediumPreset]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayMs_MediumPreset]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayMs_ShortPreset]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayMs_ShortPreset]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayPreset_AllCasesHaveDisplayNames]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayPreset_AllCasesHaveDisplayNames]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayPreset_MillisecondsValues]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDHoldDelayPreset_MillisecondsValues]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDTriggerMode_AllCasesHaveDisplayNames]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testContextHUDTriggerMode_AllCasesHaveDisplayNames]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testIsValidPort_AcceptsUserPorts]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testIsValidPort_AcceptsUserPorts]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testIsValidPort_RejectsOutOfRange]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testIsValidPort_RejectsOutOfRange]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testIsValidPort_RejectsSystemPorts]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testIsValidPort_RejectsSystemPorts]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testKeyLabelStyle_AllCasesHaveDisplayNames]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testKeyLabelStyle_AllCasesHaveDisplayNames]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testKeyLabelStyle_InitFromRawValue]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testKeyLabelStyle_InitFromRawValue]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testKeyLabelStyle_RawValues]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testKeyLabelStyle_RawValues]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testKindaVimLeaderHUDMode_AllCasesHaveDisplayNames]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testKindaVimLeaderHUDMode_AllCasesHaveDisplayNames]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testPreferredProtocol_AlwaysTCP]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testPreferredProtocol_AlwaysTCP]' passed (0.000 seconds).
    Test Case '-[KeyPathTests.PreferencesServiceTests testResetCommunicationSettings_RestoresDefaults]' started.
    Test Case '-[KeyPathTests.PreferencesServiceTests testResetCommunicationSettings_RestoresDefaults]' passed (0.000 seconds).
    Test Suite 'PreferencesServiceTests' passed at 2026-09-13 09:37:47.166.
    Executed 20 tests, with 0 failures (0 unexpected) in 0.007 (0.008) seconds
    Test Suite 'KeyPathPackageTests.xctest' passed at 2026-09-13 09:37:47.166.
    Executed 20 tests, with 0 failures (0 unexpected) in 0.007 (0.008) seconds
    Test Suite 'Selected tests' passed at 2026-09-13 09:37:47.166.
    Executed 20 tests, with 0 failures (0 unexpected) in 0.007 (0.009) seconds
    􀟈 Test run started.
    􀄵 Testing Library Version: 1902
    􀄵 Target Platform: arm64e-apple-macos14.0
    􁁛 Test run with 0 tests in 0 suites passed after 0.001 seconds.
    ✅ All tests passed (21 passed)
    📊 Runner summary: lane=full exit=0 prebuild=1 disable_xctest=0 reset_module_cache=0 build=4s test=2s total=6s build_log=252 bytes build_log_swift_warnings=0 build_log_module_cache_warnings=0 log=5337 bytes test_log_swift_warnings=0 test_log_module_cache_warnings=0 test_log_app_warnings=0 test_log_app_errors=0
  • Astra review: approved

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T16:39:32.567384Z 6b91060 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown

One reliability concern with the fallback:

canonicalDefaults is a computed property, so UserDefaults(suiteName: "com.keypath.KeyPath") is re-evaluated on every access rather than resolved once and cached. If the suite is only transiently unavailable (e.g. a brief app-group/entitlement race at launch), some calls could fall back to .standard while later calls succeed and use the real suite — silently splitting preference reads/writes across two different UserDefaults domains with no logging to indicate it happened. Per this repo's bug-investigation policy (checking logs first), a silent fallback here would be very hard to diagnose in the field.

Suggest:

  • Log a warning (existing KeyPath logging facility) when suiteDefaults is nil, so a real app-group misconfiguration is visible in keypath-debug.log rather than silently masked.
  • Consider resolving/caching the suite UserDefaults once (e.g. lazily) instead of re-attempting UserDefaults(suiteName:) on every property access, to avoid inconsistent domain selection across calls within the same run.

Test coverage for the new fallback path (testCanonicalDefaultsFallsBackToStandardWhenSuiteIsUnavailable) looks good.

@malpern
malpern force-pushed the codex/preferences-defaults-fallback branch from 6b91060 to 6294586 Compare September 13, 2026 16:51
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown

One correctness concern: canonicalDefaults is a computed property, re-evaluated on every access (Sources/KeyPathAppKit/Services/Configuration/PreferencesService.swift:179-186). If UserDefaults(suiteName:) fails transiently at launch but succeeds on a later call, reads/writes can silently bounce between .standard and the real app-group suite. A preference written during the fallback window (.standard) would then appear "reset" once the suite becomes available and subsequent calls resolve there instead — a subtle, hard-to-reproduce data-loss bug rather than the crash this fix intends to prevent.

Suggest caching the resolved UserDefaults instance once per process (e.g. a static let/lazy value) instead of re-resolving on every access, and logging when the suite is unavailable so the fallback isn't silent. The new test only exercises canonicalDefaults(using:) with an explicit nil, not the actual nil-return path from UserDefaults(suiteName:), so this recomputation behavior isn't covered.

🤖 Generated with Claude Code

@malpern
malpern force-pushed the codex/preferences-defaults-fallback branch from 6294586 to ac8ff6f Compare September 13, 2026 17:15
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown

Reliability note on the fallback: canonicalDefaults is a computed property that calls UserDefaults(suiteName:) fresh on every access rather than caching the result. If the suite is transiently unavailable early in launch, some early reads/writes silently land in .standard while later calls (once the suite becomes available) will read/write the shared com.keypath.KeyPath suite instead. Since this suite is presumably an app-group container used to share preferences with the privileged helper/other processes, a value written during the fallback window would be invisible to anything reading the suite directly — a hard-to-diagnose "preference didn't take effect" bug with no logging to flag when the fallback path was hit. Consider (a) logging when suiteDefaults is nil so this is diagnosable, and/or (b) caching the resolved UserDefaults instance (e.g. lazily) so behavior is consistent for the lifetime of the process instead of varying call-to-call.

Test coverage is otherwise reasonable — the new test exercises the nil fallback path directly.

@malpern
malpern merged commit 0e7195d into master Sep 13, 2026
3 checks passed
@malpern
malpern deleted the codex/preferences-defaults-fallback branch September 13, 2026 17:19
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