diff --git a/apps/frontend/src/main/ipc-handlers/context/memory-data-handlers.ts b/apps/frontend/src/main/ipc-handlers/context/memory-data-handlers.ts index 302c5671..153bbeb0 100644 --- a/apps/frontend/src/main/ipc-handlers/context/memory-data-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/context/memory-data-handlers.ts @@ -31,8 +31,12 @@ export function loadFileBasedMemories( const recentSpecDirs = readdirSync(specsDir) .filter((f: string) => { - const specPath = path.join(specsDir, f); - return statSync(specPath).isDirectory(); + try { + const specPath = path.join(specsDir, f); + return statSync(specPath).isDirectory(); + } catch { + return false; + } }) .sort() .reverse() @@ -118,8 +122,12 @@ export function searchFileBasedMemories( const allSpecDirs = readdirSync(specsDir) .filter((f: string) => { - const specPath = path.join(specsDir, f); - return statSync(specPath).isDirectory(); + try { + const specPath = path.join(specsDir, f); + return statSync(specPath).isDirectory(); + } catch { + return false; + } }); for (const specDir of allSpecDirs) { diff --git a/apps/frontend/src/main/ipc-handlers/context/memory-status-handlers.ts b/apps/frontend/src/main/ipc-handlers/context/memory-status-handlers.ts index 85c2f83c..019afbf9 100644 --- a/apps/frontend/src/main/ipc-handlers/context/memory-status-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/context/memory-status-handlers.ts @@ -34,8 +34,13 @@ export function loadGraphitiStateFromSpecs( const specDirs = readdirSync(specsDir) .filter((f: string) => { - const specPath = path.join(specsDir, f); - return statSync(specPath).isDirectory(); + try { + const specPath = path.join(specsDir, f); + return statSync(specPath).isDirectory(); + } catch { + // Directory was deleted or inaccessible - skip it + return false; + } }) .sort() .reverse(); diff --git a/apps/frontend/src/main/memory-service.ts b/apps/frontend/src/main/memory-service.ts index 00e88936..cde18fb4 100644 --- a/apps/frontend/src/main/memory-service.ts +++ b/apps/frontend/src/main/memory-service.ts @@ -203,6 +203,9 @@ async function executeQuery( const [pythonExe, baseArgs] = parsePythonCommand(pythonCmd); return new Promise((resolve) => { + // Promise guard flag to prevent double resolution + let resolved = false; + const fullArgs = [...baseArgs, scriptPath, command, ...args]; // Get Python environment (includes PYTHONPATH for bundled/venv packages) @@ -227,7 +230,20 @@ async function executeQuery( stderr += data.toString('utf-8'); }); + // Single timeout mechanism to avoid race condition + const timeoutId = setTimeout(() => { + if (!resolved) { + resolved = true; + proc.kill(); + resolve({ success: false, error: 'Query timed out' }); + } + }, timeout); + proc.on('close', (code) => { + if (resolved) return; + resolved = true; + clearTimeout(timeoutId); + // The Python script outputs JSON to stdout (even for errors) // Always try to parse stdout first to get the actual error message if (stdout) { @@ -254,14 +270,11 @@ async function executeQuery( }); proc.on('error', (err) => { + if (resolved) return; + resolved = true; + clearTimeout(timeoutId); resolve({ success: false, error: err.message }); }); - - // Handle timeout - setTimeout(() => { - proc.kill(); - resolve({ success: false, error: 'Query timed out' }); - }, timeout); }); } @@ -351,6 +364,9 @@ async function executeSemanticQuery( } return new Promise((resolve) => { + // Promise guard flag to prevent double resolution + let resolved = false; + const fullArgs = [...baseArgs, scriptPath, 'semantic-search', ...args]; const proc = spawn(pythonExe, fullArgs, { stdio: ['ignore', 'pipe', 'pipe'], @@ -369,7 +385,20 @@ async function executeSemanticQuery( stderr += data.toString('utf-8'); }); + // Single timeout mechanism to avoid race condition + const timeoutId = setTimeout(() => { + if (!resolved) { + resolved = true; + proc.kill(); + resolve({ success: false, error: 'Semantic search timed out' }); + } + }, timeout); + proc.on('close', (code) => { + if (resolved) return; + resolved = true; + clearTimeout(timeoutId); + // The Python script outputs JSON to stdout (even for errors) if (stdout) { try { @@ -393,13 +422,11 @@ async function executeSemanticQuery( }); proc.on('error', (err) => { + if (resolved) return; + resolved = true; + clearTimeout(timeoutId); resolve({ success: false, error: err.message }); }); - - setTimeout(() => { - proc.kill(); - resolve({ success: false, error: 'Semantic search timed out' }); - }, timeout); }); } diff --git a/apps/frontend/src/renderer/App.tsx b/apps/frontend/src/renderer/App.tsx index e21922b1..76b110aa 100644 --- a/apps/frontend/src/renderer/App.tsx +++ b/apps/frontend/src/renderer/App.tsx @@ -38,6 +38,7 @@ import { Roadmap } from './components/Roadmap'; import { Context } from './components/Context'; import { Ideation } from './components/Ideation'; import { Insights } from './components/Insights'; +import { ErrorBoundary } from './components/ui/error-boundary'; import { GitHubIssues } from './components/GitHubIssues'; import { GitLabIssues } from './components/GitLabIssues'; import { GitHubPRs } from './components/github-prs'; @@ -895,7 +896,9 @@ export function App() { )} {activeView === 'context' && (activeProjectId || selectedProjectId) && ( - + + + )} {activeView === 'ideation' && (activeProjectId || selectedProjectId) && ( diff --git a/apps/frontend/src/renderer/components/context/MemoryCard.tsx b/apps/frontend/src/renderer/components/context/MemoryCard.tsx index 4a9d0268..46260083 100644 --- a/apps/frontend/src/renderer/components/context/MemoryCard.tsx +++ b/apps/frontend/src/renderer/components/context/MemoryCard.tsx @@ -276,7 +276,9 @@ export function MemoryCard({ memory }: MemoryCardProps) {
{parsed.discoveries.patterns_discovered.map((pattern, idx) => { - const text = typeof pattern === 'string' ? pattern : pattern.pattern; + const text = typeof pattern === 'string' + ? pattern + : (pattern?.pattern || pattern?.applies_to || JSON.stringify(pattern)); return text ? ( {text} @@ -293,7 +295,9 @@ export function MemoryCard({ memory }: MemoryCardProps) {
    {parsed.discoveries.gotchas_discovered.map((gotcha, idx) => { - const text = typeof gotcha === 'string' ? gotcha : gotcha.gotcha; + const text = typeof gotcha === 'string' + ? gotcha + : (gotcha?.gotcha || JSON.stringify(gotcha)); return text ? ( {text} ) : null;