Harden crash-prone force-unwraps and traps - #22
Merged
Conversation
Defensive fixes from the code audit — four independent crash/robustness findings across disjoint files. Each converts a runtime-fallible force-unwrap or `try!` into graceful degradation, per .claude/rules/swift-style.md. - AppEnvironment: the on-disk ModelContainer recreate retry was `try!`. If store deletion silently fails or the store is genuinely incompatible, the retry throws and traps → unrecoverable boot loop. Wrap the retry in a second do/catch and fall back to an in-memory container so the app still boots (no persistence this session). The last-resort in-memory `try!` cannot realistically fail (schema already validated). - SessionManager.startDemoSession: in-memory demo container built with `try!`. Convert to `guard let try?` + log and bail. buildTabViewModels already guards `session`, and RootView now tolerates a nil session, so demo entry degrades to a no-op instead of crashing. - MDISymbolMapper.loadMapping: `try! Data(contentsOf:)` and `try! decode` aborted the process on any read/decode failure of the bundled MDI JSON. Degrade to an empty map + log (icons fall back to unmapped). Added `import HemeraLog`. Missing-resource preconditionFailure downgraded to a log for consistency. Added bundledMaps_decodeToNonEmpty regression test. - AuthenticatedWebView: force-unwrapped URLComponents decomposition and recomposition on a credential-derived server URL. Guard both; fall back to the original url (omitting external_auth=1) if decomposition fails. - RootView: `.authenticated` case force-unwrapped ServiceLocator.shared.session. Safety was implicit in handler-registration ordering. Make it local with `if let session`, rendering Color.clear otherwise. Ordering unchanged. Tests: added inMemoryDemoContainer_withCurrentSchema_buildsAndIsQueryable and MDISymbolMapperTests.bundledMaps_decodeToNonEmpty. ModelContainer-failure and RootView rendering paths are not unit-testable; verified by clean build + reasoning. HemeraTests green on iPhone 16 Pro. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Project convention: multi-line explanatory comments use the `/** ... */` block form. Converts the two multi-line `//` comments introduced in the crash-hardening change (RootView's nil-session fallback rationale, and the demo-container test's intent note). Comment-only change; build verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Defensive crash-hardening from the code audit — five independent findings across disjoint files, each converting a runtime-fallible force-unwrap or
try!into graceful degradation (per.claude/rules/swift-style.md).AppEnvironment— the on-diskModelContainerrecreate-retry wastry!; a repeated failure trapped into a boot loop. Wrapped the retry in a seconddo/catchthat falls back to an in-memory container so the app still boots (no persistence this session). The last-resort in-memorytry!cannot realistically fail (schema already validated), anddeleteStorealready removed the on-disk store so the next launch recovers to on-disk.SessionManager.startDemoSession— in-memory demo container wastry!. Nowguard let try?+ log + bail.buildTabViewModelsalready guardssession, andRootViewnow tolerates a nil session, so demo entry degrades instead of crashing.MDISymbolMapper.loadMapping—try! Data(contentsOf:)andtry! decodeaborted the process on any bundled-JSON read/decode failure. Now degrades to an empty map + logs (icons fall back to unmapped). Addedimport HemeraLog.AuthenticatedWebView— force-unwrappedURLComponentsdecomposition and recomposition on a credential-derived server URL. Both guarded; falls back to the originalurl(omittingexternal_auth=1) if decomposition fails.RootView—.authenticatedcase force-unwrappedServiceLocator.shared.session. Made local withif let session { … } else { Color.clear }; handler-registration ordering left unchanged.Why
These are the only reachable force-unwraps/traps on runtime-derived input in the touched areas. None is an observed live crash — they're defensive hardening so malformed stored credentials, an incompatible/undeletable store, a corrupt bundled resource, or a teardown-timing edge degrade gracefully rather than aborting the process.
Notes
SessionManagerDemoTests.inMemoryDemoContainer_withCurrentSchema_buildsAndIsQueryableandMDISymbolMapperTests.bundledMaps_decodeToNonEmpty.ModelContainer-init-failure and SwiftUI-rendering paths aren't unit-testable per.claude/rules/testing.md— covered by clean build + reasoning.HemeraTestsgreen on iPhone 16 Pro.🤖 Generated with Claude Code