fix(ollama): stop infinite subprocess spawning from useEffect re-render loop (#1716)

* fix(graphiti): migrate graphiti_memory imports to canonical paths

The `graphiti_memory.py` shim was removed during the backend refactor
(commit 11fcdf42) but consumers were never updated. This caused all
`from graphiti_memory import ...` to fail silently (caught by
try/except ImportError), preventing the Graphiti memory system from
initializing for any project.

Migrate the 3 remaining import sites to use the canonical module path
`integrations.graphiti.memory` instead of the deleted shim.

Closes #1220

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* style: combine docstring import example into single line

Address Gemini Code Assist review suggestion to merge two import
examples from the same module into one line.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ollama): stop infinite subprocess spawning from useEffect re-render loop

OllamaModelSelector's checkInstalledModels was a plain async function used
as a useEffect dependency. Since the function reference changed on every
render, the effect fired continuously — each iteration spawning 2-3 Python
subprocesses via executeOllamaDetector, causing hundreds of processes.

- Wrap checkInstalledModels in useCallback with [baseUrl] dependency so
  the useEffect only re-runs when baseUrl actually changes
- Add a 2s deduplication cache to executeOllamaDetector as a safety net:
  identical command+baseUrl calls within the TTL return the same promise
  instead of spawning a new subprocess

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
This commit is contained in:
Quentin Veys
2026-02-05 14:37:53 +01:00
committed by GitHub
parent df528f0650
commit acb131b721
2 changed files with 41 additions and 3 deletions
@@ -201,6 +201,10 @@ function getOllamaInstallCommand(): string {
* Spawns a subprocess to run Ollama detection/management commands with a 10-second timeout.
* Used to check Ollama status, list models, and manage downloads.
*
* Includes deduplication: identical command+baseUrl requests within 2s return the cached
* result/promise instead of spawning a new subprocess. This prevents runaway subprocess
* spawning from React re-render loops.
*
* Supported commands:
* - 'check-status': Verify Ollama service is running
* - 'list-models': Get all available models
@@ -212,9 +216,43 @@ function getOllamaInstallCommand(): string {
* @param {string} [baseUrl] - Optional Ollama API base URL (defaults to http://localhost:11434)
* @returns {Promise<{success, data?, error?}>} Result object with success flag and data/error
*/
// Deduplication cache to prevent rapid-fire subprocess spawning (e.g., from React re-render loops)
const ollamaDetectorCache = new Map<string, { promise: Promise<{ success: boolean; data?: unknown; error?: string }>; timestamp: number }>();
const OLLAMA_CACHE_TTL_MS = 2000; // Cache results for 2 seconds
async function executeOllamaDetector(
command: string,
baseUrl?: string
): Promise<{ success: boolean; data?: unknown; error?: string }> {
// Deduplication: return cached promise for identical requests within TTL
const cacheKey = `${command}:${baseUrl || 'default'}`;
const cached = ollamaDetectorCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < OLLAMA_CACHE_TTL_MS) {
if (process.env.DEBUG) {
console.log('[OllamaDetector] Returning cached result for:', command);
}
return cached.promise;
}
const promise = executeOllamaDetectorImpl(command, baseUrl);
ollamaDetectorCache.set(cacheKey, { promise, timestamp: Date.now() });
// Clean up cache entry after TTL
promise.finally(() => {
setTimeout(() => {
const entry = ollamaDetectorCache.get(cacheKey);
if (entry && entry.promise === promise) {
ollamaDetectorCache.delete(cacheKey);
}
}, OLLAMA_CACHE_TTL_MS);
});
return promise;
}
async function executeOllamaDetectorImpl(
command: string,
baseUrl?: string
): Promise<{ success: boolean; data?: unknown; error?: string }> {
// Use configured Python path (venv if ready, otherwise bundled/system)
// Note: ollama_model_detector.py doesn't require dotenv, but using venv is safer
@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useRef, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import {
Check,
@@ -133,7 +133,7 @@ export function OllamaModelSelector({
* @param {AbortSignal} [abortSignal] - Optional abort signal to cancel the request
* @returns {Promise<void>}
*/
const checkInstalledModels = async (abortSignal?: AbortSignal) => {
const checkInstalledModels = useCallback(async (abortSignal?: AbortSignal) => {
setIsLoading(true);
setError(null);
setOllamaState('checking');
@@ -217,7 +217,7 @@ export function OllamaModelSelector({
setIsLoading(false);
}
}
};
}, [baseUrl]);
/**
* Install Ollama by opening terminal with the official install command.