Repository navigation
Fix crashes from forced casts on explicit-null Nightscout fields (full-schema uploader compatibility) - #794
markjoldham wants to merge 3 commits into
Conversation
lastDeviceStatus?["pump"], ["loop"], and ["openaps"] were force-cast (as!) to [String: AnyObject]?. When Nightscout returns a non-dictionary value for one of these keys (e.g. explicit null), the forced cast throws a Swift runtime fatal error (swift_dynamicCastFailure), crashing the app on the main thread shortly after launch during device status processing. Switched to safe optional casts (as?) so a missing or unexpected value is simply treated as absent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SMB/Carbs/Bolus/Notes processing and StatsDataFetcher checked currentEntry["timestamp"] != nil before force-casting it to String, but that check passes for an explicit JSON null (NSNull), which is not nil in Swift. Uploaders that always include a "timestamp" key (set to null when unused, falling back to created_at) made every treatment with a null timestamp crash with swift_dynamicCastFailure on the main thread. Replaced with a single safe-cast fallback chain (timestamp as? String ?? created_at as? String) that correctly skips a null/missing timestamp and falls through to created_at instead of crashing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
1 similar comment
|
|
|
Thanks for the thorough write-up in #793, the root cause analysis is spot on. The base is now What is not fixed on Could you merge One optional addition while you are in there: |
|
@markjoldham I know you are busy, so I would push the rebaselined version of your change but I don't have privileges. |
|
closing this in favor of #797. |
Problem
LoopFollow crashes on launch, and again during normal treatment-graph loading, when the Nightscout backend is fed full-schema documents (every field present,
nullwhen unused) instead of the traditional sparse style (unused fields simply omitted). I hit this after reconfiguring my pipeline so Nocturne, not Trio, writes to Nightscout directly (previously: Trio → Nightscout, with Nocturne only reading from Nightscout; now: Trio → Nocturne → Nightscout). The exact mechanism Nocturne uses to write to Nightscout isn't confirmed, but the resulting change in document shape is, against my own Nightscout data (see below).Full root-cause writeup with data: see #793.
Fixes #793.
Root cause (short version)
JSONSerializationdecodes a JSONnullasNSNull, a real non-nil object, not Swift'snil. Several places in the Nightscout-parsing code do:!= nilis true forNSNull, so the force-cast (as!) that follows crashes withswift_dynamicCastFailurewhenever the field is explicitlynullrather than absent. Full-schema uploaders (like Nocturne) always include every field,nullwhen unused — e.g.devicestatus.loopis now always present asnullfor a non-Loop rig, andtreatments[].timestampis now always present asnullalongsidecreated_at. Sparse uploaders never sent those keys at all, so this was never previously reachable.Confirmed against two real crash reports (an initial crash, and a second, distinct crash that surfaced only after the first was fixed) and against live production data spanning the uploader-switch transition (pinpointed to an instantaneous cutover at
2026-10-26T02:13:47Z).What changed
Controllers/Nightscout/DeviceStatus.swift—pump/loop/openapstop-level casts changed fromas!toas?, so an explicit-null or unexpected value is treated as absent instead of crashing.Controllers/Nightscout/Treatments/{SMB,Carbs,Bolus,Notes}.swiftandStats/StatsDataFetcher.swift(3 call sites) — thetimestamp/created_atpresence-check-then-force-cast pattern replaced with a single safe-cast fallback chain:Testing
.ipsreports (initial crash:DeviceStatus.swift; second crash, which surfaced only after the DeviceStatus fix was already applied: treatment-timestamp).devicestatusandtreatmentsshapes shown in the issue.Known follow-up (not included in this PR)
The same
!= nil+as!pattern exists in a few other spots that aren't currently reachable with my data (Loop-only device-status fields, sensor/cannula-age parsing) but are equally fragile under full-schema uploaders. Listed in the issue for a possible follow-up pass.🤖 Generated with Claude Code