Skip to content

fix(desktop): accept both action_items and items keys in ActionItemsListResponse#6414

Merged
beastoin merged 2 commits into
mainfrom
fix/staged-tasks-decode-6387
Apr 8, 2026
Merged

fix(desktop): accept both action_items and items keys in ActionItemsListResponse#6414
beastoin merged 2 commits into
mainfrom
fix/staged-tasks-decode-6387

Conversation

@beastoin
Copy link
Copy Markdown
Collaborator

@beastoin beastoin commented Apr 7, 2026

Summary

  • ActionItemsListResponse custom Decodable init(from:) now tries action_items first, falls back to items — fixing getStagedTasks() which hits /v1/staged-tasks returning {"items": [...]}.
  • Changed conformance from Codable to Decodable (type is never encoded).

Tests

7 unit tests in ActionItemsListResponseTests.swift:

  • ✅ Decode with action_items key (/v1/action-items shape)
  • ✅ Decode with items key (/v1/staged-tasks shape)
  • ✅ Both keys present → action_items takes precedence
  • ✅ Neither key present → decode throws
  • ✅ Missing has_more → decode throws
  • ✅ Empty action_items array
  • ✅ Empty items array

All 7 tests pass locally via xcrun swift test --filter ActionItemsListResponse.

Risks / Edge Cases

  • If a payload contains both action_items and items with different values, action_items wins.
  • No encode conformance — type changed to Decodable only. If encoding is ever needed, add Encodable conformance separately.

Review Cycle Changes

  • Round 1: Reviewer approved (LGTM)
  • Round 2: Tester requested unit tests → added 7 decode tests covering both key variants, error paths, and edge cases

Fixes #6387

🤖 Generated with Claude Code

…istResponse

/v1/action-items returns {"action_items": ...} while /v1/staged-tasks
returns {"items": ...}. After PR #6380 changed the CodingKey to expect
action_items, getStagedTasks() broke with a decode error.

Custom init(from:) tries action_items first, falls back to items.

Fixes #6387

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 7, 2026

Greptile Summary

This PR fixes a decode failure in TaskDeduplicationService caused by PR #6380 changing ActionItemsListResponse's coding key to action_items, while getStagedTasks() decodes /v1/staged-tasks responses that use items. The fix adds a custom init(from:) that tries action_items first and falls back to items, and appropriately narrows the conformance from Codable to Decodable.

Confidence Score: 5/5

Safe to merge — the fix is minimal, correct, and directly addresses the root cause with no P0/P1 issues.

All observations are P2 style suggestions (e.g., defensive decoding of has_more). The decode logic correctly uses decodeIfPresent for the optional action_items key with a required decode fallback for items, ensuring a clear DecodingError.keyNotFound if both keys are absent. The Codable→Decodable narrowing is appropriate since this type is never encoded.

No files require special attention.

Vulnerabilities

No security concerns identified.

Important Files Changed

Filename Overview
desktop/Desktop/Sources/APIClient.swift Adds custom init(from:) decoder that tries 'action_items' first then falls back to 'items'; narrows conformance from Codable to Decodable — correct and minimal fix for the staged-tasks decode failure.

Sequence Diagram

sequenceDiagram
    participant Caller as Caller (TasksStore / TaskDeduplicationService)
    participant APIClient as APIClient
    participant Backend as Backend (api.omi.me)
    participant Decoder as Custom init(from:)

    Caller->>APIClient: getActionItems(...)
    APIClient->>Backend: GET /v1/action-items
    Backend-->>APIClient: {"action_items": [...], "has_more": true}
    APIClient->>Decoder: decode ActionItemsListResponse
    Note right of Decoder: decodeIfPresent(.actionItems) succeeds<br/>self.items = actionItems
    Decoder-->>Caller: ActionItemsListResponse(items: [...])

    Caller->>APIClient: getStagedTasks(...)
    APIClient->>Backend: GET /v1/staged-tasks
    Backend-->>APIClient: {"items": [...], "has_more": false}
    APIClient->>Decoder: decode ActionItemsListResponse
    Note right of Decoder: decodeIfPresent(.actionItems) → nil<br/>fallback: decode(.items)<br/>self.items = items
    Decoder-->>Caller: ActionItemsListResponse(items: [...])
Loading

Reviews (1): Last reviewed commit: "fix(desktop): accept both action_items a..." | Re-trigger Greptile

Cover both action_items and items key variants, precedence when both
present, missing key errors, and empty arrays.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@beastoin
Copy link
Copy Markdown
Collaborator Author

beastoin commented Apr 7, 2026

All checkpoints passed — ready for merge

Checkpoint Status
CP1 Issue understood
CP2 Workspace setup
CP3 Exploration
CP4 CODEx consult
CP5 Implementation
CP6 PR created
CP7 Reviewer approved ✅ PR_APPROVED_LGTM
CP8 Tester approved ✅ TESTS_APPROVED (7/7 tests pass)
CP9 Live test ⏭️ Skipped (no audio/streaming paths)

Changes:

  • ActionItemsListResponse custom decoder accepts both action_items and items keys
  • Changed conformance from Codable to Decodable
  • 7 unit tests covering both key variants, precedence, error paths, and empty arrays

Awaiting human merge approval.

by AI for @beastoin

@beastoin
Copy link
Copy Markdown
Collaborator Author

beastoin commented Apr 8, 2026

E2E Test Results — Mac Mini (pr6414 named bundle)

Build & Launch

  • Built from branch fix/staged-tasks-decode-6387 (commit 3cf781a7c)
  • Named bundle: pr6414.app (com.omi.pr6414)
  • Signed in with Google, app launched successfully

Test Results

1. TaskDedup / Staged Tasks decode (the fix)

[00:18:13.109] TaskDedup: Service started (staged tasks only)
[00:18:14.275] TasksStore: Staged tasks migration already completed for user yDq...
[00:18:14.563] TaskPromotion: No more promotions — No staged tasks available

getStagedTasks()/v1/staged-tasks decoded successfully (no decode errors). Previously this would crash with keyNotFound("action_items").

2. Tasks page

[00:18:13.325] TasksStore: Loaded 6 incomplete tasks from local cache
[00:18:13.740] TasksStore: Fetched 6 incomplete tasks from API
[00:18:14.275] TasksStore: Reconciled on load: all local tasks match API

3. Goals

[00:18:13.680] GoalStorage: Synced 4 goals from server

4. Decode errors ✅ None

grep -i "decode.*error\|typeMismatch\|keyNotFound\|dataCorrupted" → (empty)

Screenshots

Dashboard (Tasks + Goals loaded)
Dashboard

Tasks page
Tasks

Unit Tests

7/7 pass: xcrun swift test --filter ActionItemsListResponse

by AI for @beastoin

@beastoin beastoin merged commit a98cfb9 into main Apr 8, 2026
2 checks passed
@beastoin beastoin deleted the fix/staged-tasks-decode-6387 branch April 8, 2026 00:43
@beastoin
Copy link
Copy Markdown
Collaborator Author

beastoin commented Apr 8, 2026

lgtm

@beastoin
Copy link
Copy Markdown
Collaborator Author

beastoin commented Apr 8, 2026

Desktop App Walkthrough — Omi Beta v0.11.251 (Mac Mini)

Pages Tested

Page Status Notes
Dashboard Tasks, Goals, Conversations all render
Chat History loads, messages display correctly
Memories Brain Map renders, memory list loads
Tasks Task list loads with 1 active task
Rewind Screenshot timeline renders, captures working
Apps Imports + Exports sections both load with app cards
Settings All sections accessible
Conversations List loads with proper dates and durations

Errors Found in Logs (v0.11.251)

1. TaskDedup decode error (fixed by this PR) 🔧

Decoding error - key 'action_items' not found
TaskDedup: Failed to fetch staged tasks: keyNotFound("action_items")

This is the exact bug #6387 fixes. Will be resolved in the next release.

2. Backend settings decode error ⚠️

Decoding error - key 'enabled' not found
Failed to load backend settings: The data couldn't be read because it is missing.

Backend settings response missing enabled key — similar pattern to the action_items issue. New issue candidate.

3. SQLite disk I/O errors (widespread) ⚠️

RewindIndexer: Failed to process CGImage frame: databaseNotInitialized
TasksStore: Auto-refresh failed: SQLite error 10: disk I/O error
Focus: Failed to load goals/memories/tasks for context: SQLite error 10
RewindDatabase: 5 consecutive I/O errors, closing database for recovery

Multiple subsystems hit SQLite I/O errors — RewindIndexer, TasksStore, Focus, Memory, TranscriptionRetry. The database eventually recovers but this causes data loss for Rewind frames captured during the error window.

4. TranscriptionRetryService: Crashed sessions ℹ️

TranscriptionRetryService: Deleting empty crashed session 224/225/226/227
Stats - total=71, pending=0, failed=15, completed=56

15 out of 71 transcription sessions failed (21% failure rate).

5. AppleNotesReaderService: Authorization denied ℹ️

Failed reading Notes store: SQLite error 23: authorization denied

Missing Full Disk Access permission for reading Apple Notes.

6. WebSocket disconnect ℹ️

TranscriptionService: Receive error: The operation couldn't be completed. Socket is not connected

Single WS disconnect event.

Screenshots

Recommendations

  1. fix(desktop): TaskDedup fails to decode staged-tasks response after action_items key change #6387 fix (this PR) — already merged, will resolve TaskDedup decode error in next release
  2. Backend settings enabled key — file new issue for missing key decode error
  3. SQLite I/O errors — investigate root cause (possibly file locking between multiple running Omi instances, or disk pressure)

by AI for @beastoin

Glucksberg pushed a commit to Glucksberg/omi-local that referenced this pull request Apr 28, 2026
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.

fix(desktop): TaskDedup fails to decode staged-tasks response after action_items key change

1 participant