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() {