From 2f241ce494b4e660058ba371e813875f9e00d4d2 Mon Sep 17 00:00:00 2001 From: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com> Date: Fri, 8 May 2026 00:56:19 +0530 Subject: [PATCH] fix(memory): graceful empty statuses when DB unavailable (#1292) (#1328) --- app/src/services/memorySyncService.test.ts | 10 ++-- app/src/services/memorySyncService.ts | 7 ++- src/openhuman/memory/sync_status/rpc.rs | 59 ++++++++++++++++++++-- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/app/src/services/memorySyncService.test.ts b/app/src/services/memorySyncService.test.ts index 073d655fe..331e84ae8 100644 --- a/app/src/services/memorySyncService.test.ts +++ b/app/src/services/memorySyncService.test.ts @@ -44,13 +44,15 @@ describe('memorySyncService.memorySyncStatusList', () => { await expect(memorySyncStatusList()).rejects.toThrow('rpc boom'); }); - it('throws on malformed response (missing statuses[])', async () => { + it('returns empty array on malformed response (missing statuses[])', async () => { mockCallCoreRpc.mockResolvedValueOnce({ wrong: 'shape' }); - await expect(memorySyncStatusList()).rejects.toThrow(/missing statuses/); + const out = await memorySyncStatusList(); + expect(out).toEqual([]); }); - it('throws on null response', async () => { + it('returns empty array on null response', async () => { mockCallCoreRpc.mockResolvedValueOnce(null); - await expect(memorySyncStatusList()).rejects.toThrow(/missing statuses/); + const out = await memorySyncStatusList(); + expect(out).toEqual([]); }); }); diff --git a/app/src/services/memorySyncService.ts b/app/src/services/memorySyncService.ts index a8550c43a..c907dc752 100644 --- a/app/src/services/memorySyncService.ts +++ b/app/src/services/memorySyncService.ts @@ -57,8 +57,11 @@ export async function memorySyncStatusList(): Promise { throw err; } if (!resp || !Array.isArray(resp.statuses)) { - errLog('memory_sync_status_list: malformed response (missing statuses[]): %O', resp); - throw new Error('Invalid response from openhuman.memory_sync_status_list: missing statuses[]'); + errLog( + 'memory_sync_status_list: malformed response (missing statuses[]), returning empty: %O', + resp + ); + return []; } log('memory_sync_status_list: received %d row(s)', resp.statuses.length); return resp.statuses; diff --git a/src/openhuman/memory/sync_status/rpc.rs b/src/openhuman/memory/sync_status/rpc.rs index de7653430..9c2a4f225 100644 --- a/src/openhuman/memory/sync_status/rpc.rs +++ b/src/openhuman/memory/sync_status/rpc.rs @@ -42,7 +42,7 @@ pub async fn status_list_rpc(config: &Config) -> Result = tokio::task::spawn_blocking(move || { + let statuses: Vec = match tokio::task::spawn_blocking(move || { with_connection(&config, |conn| -> anyhow::Result> { // Provider parsed from `source_id` prefix (substring before // first ':'); falls back to `source_kind` when no prefix. @@ -130,8 +130,23 @@ pub async fn status_list_rpc(config: &Config) -> Result rows, + // DB unavailable (open/migration failure) or query error: return empty + // so the schema contract (`statuses` array) is always satisfied. + Ok(Err(e)) => { + tracing::warn!( + "[memory_sync_status][rpc] DB query failed, returning empty statuses: {e:#}" + ); + vec![] + } + Err(e) => { + tracing::warn!( + "[memory_sync_status][rpc] spawn_blocking join error, returning empty statuses: {e}" + ); + vec![] + } + }; tracing::debug!( "[memory_sync_status][rpc] status_list returning {} row(s)", @@ -143,3 +158,41 @@ pub async fn status_list_rpc(config: &Config) -> Result