Skip to content

fix: detect and skip Limitless firmware diagnostic pages blocking flash sync#5924

Merged
mdmohsin7 merged 6 commits intoBasedHardware:mainfrom
brightshore-ventures:claude/fix-diagnostic-page-handling-AFEb9
Apr 4, 2026
Merged

fix: detect and skip Limitless firmware diagnostic pages blocking flash sync#5924
mdmohsin7 merged 6 commits intoBasedHardware:mainfrom
brightshore-ventures:claude/fix-diagnostic-page-handling-AFEb9

Conversation

@brightshore-ventures
Copy link
Copy Markdown

@brightshore-ventures brightshore-ventures commented Mar 23, 2026

Problem

The Limitless Pendant firmware (v1.1.20) writes diagnostic logs to flash storage using the same protobuf page format as audio recordings. The Omi app's Opus parser returns zero frames for these pages because they contain no audio — only RTOS thread snapshots, firmware log messages, and BLE events.

The batch downloader cannot ACK past zero-frame pages, so oldestPage gets permanently stuck and flash storage fills monotonically until the device is factory reset.

No audio is lost. These pages were never recordings — they're firmware diagnostics from idle sessions, boot sequences, and sync operations.

Related to #5733 (S2) and #5154.

Root Cause

Confirmed by capturing a failed sync simultaneously from both sides (Omi app debug log + nRF Connect BLE packet capture) and reassembling all 70 failing pages from raw BLE traffic.

Audio pages use protobuf field 0x12 inside 0x1a wrappers. Diagnostic pages use fields 0x4a/0x52/0x5a/0x62 instead. The parser looks for 0x12, never finds it, returns zero frames, and the ACK stalls.

Example page contents from the firmware:

  • <inf> scheduler_ble: Recording state set to NOT_RECORDING
  • <inf> scheduler_ble: Device mode set to IDLE_MODE
  • <inf> bt_data_comm: Finished batch upload of flash_page (firmware logging its own sync)
  • RTOS thread snapshots (storage_thread_, audio_thread_ha, poll_thread_han)

Changes

Two files.

app/lib/services/devices/limitless_connection.dart

  1. Added _hasAudioSubfields() — walks 0x1a protobuf wrappers checking for 0x12 (audio) subfields. On parse error, conservatively returns true to avoid ACKing unclassified pages.

  2. Modified zero-frame handler (line ~300) — when a page has no audio subfields, classifies it as diagnostic, adds it to _completedFlashPages with its index (enabling ACK advancement), and logs as INFO instead of WARN. Pages WITH audio subfields that still return zero frames are logged as WARN with a firstBytes hex dump for future debugging.

  3. Modified extractFramesWithSessionInfo() — previously returned null when all frames were empty, blocking the caller from seeing max_index. Now returns a valid result with empty frames and max_index so the ACK can advance past diagnostic-only batches.

  4. Proper protobuf field skipping in outer loop — the _hasAudioSubfields() outer loop now reads wire types from non-0x1a tag bytes and advances past their full payloads (varint, length-delimited, fixed32, fixed64) instead of stepping byte-by-byte. Prevents false-positive audio classification when diagnostic payload bytes happen to contain 0x1a.

  5. Warning log for null maxIndex — when a diagnostic-only batch has no page indices, logs a warning so the edge case is visible rather than silently degrading to stuck-ACK behavior.

app/lib/services/wals/flash_page_wal_sync.dart

  1. Added periodic ACK for diagnostic-only pages — when the device sent data, no audio has accumulated, and no save is pending, sends an ACK using the existing _persistBatchDuration timer. Gated on pageData != null to avoid redundant ACKs during idle iterations.

Safety

The critical invariant: audio pages are never ACKed without being processed. The pendant permanently deletes pages after ACK.

  • _hasAudioSubfields() returns true (audio) on any parse error — false positives are safe, false negatives would delete audio
  • Only pages with zero 0x12 subfields across all 0x1a wrappers are classified as diagnostic
  • Genuine audio parse failures (pages with 0x12 that still return zero frames) keep the existing behavior — logged as warnings, not ACKed

Testing

Verified the protobuf field analysis against 70 failing pages (42943–43012) reassembled from raw BLE capture data. Every page confirmed to contain only firmware diagnostics, zero audio subfields.

Environment

  • Limitless Pendant v1.0, firmware 1.1.20 (nRF53x, MCUboot)
  • Omi AI 1.0.526 (778), iPhone, iOS 26.3.1

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 23, 2026

Greptile Summary

This PR fixes a flash sync stall on Limitless Pendant firmware v1.1.20, where diagnostic pages (RTOS snapshots, firmware logs, BLE events) written in the same protobuf page format as audio were causing oldestPage to get permanently stuck, since the Opus parser returned zero frames and the batch downloader could not ACK past them.

Key changes:

  • _hasAudioSubfields() — a new parser that walks 0x1a wrappers checking for 0x12 (audio) subfields. Previously-flagged issue with byte-stepping past non-0x1a top-level fields is resolved: the else-branch now properly reads wire type and skips the full field payload, preventing false positives from 0x1a bytes embedded in diagnostic payloads.
  • Zero-frame handler — diagnostic pages (no 0x12 subfields) are added to _completedFlashPages with their index and logged as INFO; genuine audio parse failures keep the WARN path with a hex dump.
  • extractFramesWithSessionInfo() — now returns a valid map with empty frames and max_index (instead of null) so the caller can advance lastProcessedIndex. The maxIndex == null edge case is now guarded with a warning log.
  • Periodic diagnostic ACK — when accumulatedFrames.isEmpty and the persist timer elapses, an ACK is sent. This is an optimisation for long diagnostic runs; the final ACK at sync completion (line 485) acts as a safety net.

The critical invariant — audio pages are never ACKed without being processed — is preserved: _hasAudioSubfields() returns true (treat as audio) on any parse error or unknown wire type, so false positives are safe and false negatives are avoided.

Confidence Score: 5/5

  • Safe to merge — the fix is well-scoped, conservative on error paths, and all previously raised concerns have been addressed.
  • All prior review threads are resolved: the outer loop now correctly skips non-0x1a fields using wire type (eliminating false positives), maxIndex == null is guarded with a warning log, and extractFramesWithSessionInfo returns a valid result for diagnostic-only batches. The critical invariant (no audio ACKed without processing) is enforced by the conservative return true fallback in _hasAudioSubfields. The final sync-completion ACK (line 485) acts as a correctness safety net independent of the periodic diagnostic ACK timer. No new logic issues were identified.
  • No files require special attention.

Important Files Changed

Filename Overview
app/lib/services/devices/limitless_connection.dart Adds _hasAudioSubfields() to classify diagnostic vs audio protobuf pages, correctly skips non-0x1a top-level fields using wire type, and returns a valid result with empty frames + max_index from extractFramesWithSessionInfo() so the ACK can advance past diagnostic-only batches. Conservative error handling (returning true on unknown wire types and parse errors) prevents false negatives.
app/lib/services/wals/flash_page_wal_sync.dart Adds a periodic diagnostic-only ACK block that fires the existing _persistBatchDuration timer when accumulatedFrames.isEmpty. The final ACK at sync completion (line 485) serves as a safety net for diagnostic pages not yet ACKed when the timer hasn't elapsed — correctness does not depend solely on the periodic block.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Flash page received\nopusFrames = extractOpusFrames] --> B{opusFrames.isNotEmpty?}
    B -- Yes --> C[Add to _completedFlashPages\nwith audio frames]
    B -- No --> D[_hasAudioSubfields\nparse 0x1a wrappers]
    D --> E{Any 0x12\nsubfield found?}
    E -- Yes / parse error --> F[Genuine audio parse failure\nlogWarning + hex dump\nDo NOT add to completedPages]
    E -- No 0x12 found --> G[Diagnostic page\nlogEvent + add to _completedFlashPages\nwith empty opus_frames]
    C --> H[extractFramesWithSessionInfo]
    G --> H
    H --> I{allFrames.isEmpty?}
    I -- No --> J[Return frames + max_index]
    I -- Yes --> K[Return empty frames + max_index\nlog diagnostic_only event]
    K --> L{maxIndex == null?}
    L -- Yes --> M[logWarning: diagnostic_no_index]
    L -- No --> N[flash_page_wal_sync:\nlastProcessedIndex updated]
    J --> N
    N --> O{accumulatedFrames\n.isEmpty AND timer elapsed?}
    O -- Yes --> P[Send periodic diagnostic ACK\nlastSaveTime = now]
    O -- No --> Q{shouldSave AND\naudio frames?}
    Q -- Yes --> R[_saveBatchToFile\nthen ACK lastProcessedIndex]
    Q -- No --> S[Continue loop]
    P --> S
    R --> S
Loading

Reviews (2): Last reviewed commit: "fix: warn when diagnostic-only batch has..." | Re-trigger Greptile

Comment thread app/lib/services/devices/limitless_connection.dart
Comment thread app/lib/services/wals/flash_page_wal_sync.dart
Comment thread app/lib/services/devices/limitless_connection.dart
Comment thread app/lib/services/devices/limitless_connection.dart
claude added 2 commits March 23, 2026 02:17
…udioSubfields

Replace the naive pos++ for non-0x1a top-level bytes with correct
wire-type-based skipping. Prevents misalignment when diagnostic pages
contain top-level varint, length-delimited, or fixed-width fields
before the 0x1a wrappers.

https://claude.ai/code/session_01EEe9uEkE2iHmAf5fRmEL7d
Log a warning when extractFramesWithSessionInfo() processes a
diagnostic-only batch but maxIndex is null, indicating pages lacked
index metadata and the ACK cannot advance.

https://claude.ai/code/session_01EEe9uEkE2iHmAf5fRmEL7d
@brightshore-ventures
Copy link
Copy Markdown
Author

brightshore-ventures commented Mar 23, 2026

Ready for maintainer review.

Implemented two additional fixes based upon greptile bot review.

@brightshore-ventures
Copy link
Copy Markdown
Author

@greptile-apps re-review

@brightshore-ventures
Copy link
Copy Markdown
Author

@mdmohsin7 - this is my first PR. Anything else you need for this?

@mdmohsin7
Copy link
Copy Markdown
Member

Thanks for this PR @brightshore-ventures, I'll check and get back to you if anything is needed

@mdmohsin7 mdmohsin7 self-requested a review April 4, 2026 08:57
@mdmohsin7 mdmohsin7 merged commit 8de274c into BasedHardware:main Apr 4, 2026
1 check failed
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.

3 participants