Fix/cognee UI start backend server (#1380)

<!-- .github/pull_request_template.md -->

## Description
<!-- 
Please provide a clear, human-generated description of the changes in
this PR.
DO NOT use AI-generated descriptions. We want to understand your thought
process and reasoning.
-->

## Type of Change
<!-- Please check the relevant option -->
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Code refactoring
- [x] Performance improvement
- [ ] Other (please specify):

## Changes Made
<!-- List the specific changes made in this PR -->
- 
- 
- 

## Testing
<!-- Describe how you tested your changes -->

## Screenshots/Videos (if applicable)
<!-- Add screenshots or videos to help explain your changes -->

## Pre-submission Checklist
<!-- Please check all boxes that apply before submitting your PR -->
- [ ] **I have tested my changes thoroughly before submitting this PR**
- [ ] **This PR contains minimal changes necessary to address the
issue/feature**
- [ ] My code follows the project's coding standards and style
guidelines
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have added necessary documentation (if applicable)
- [ ] All new and existing tests pass
- [ ] I have searched existing PRs to ensure this change hasn't been
submitted already
- [ ] I have linked any relevant issues in the description
- [ ] My commits have clear and descriptive messages

## Related Issues
<!-- Link any related issues using "Fixes #issue_number" or "Relates to
#issue_number" -->

## Additional Notes
<!-- Add any additional notes, concerns, or context for reviewers -->

## DCO Affirmation
I affirm that all code in every commit of this pull request conforms to
the terms of the Topoteretes Developer Certificate of Origin.
This commit is contained in:
Vasilije 2025-09-11 11:31:43 -07:00 committed by GitHub
commit 353aa6df7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 36 additions and 30 deletions

View file

@ -7,7 +7,7 @@ import webbrowser
import zipfile import zipfile
import requests import requests
from pathlib import Path from pathlib import Path
from typing import Optional, Tuple from typing import Callable, Optional, Tuple
import tempfile import tempfile
import shutil import shutil
@ -326,6 +326,7 @@ def prompt_user_for_download() -> bool:
def start_ui( def start_ui(
pid_callback: Callable[[int], None],
host: str = "localhost", host: str = "localhost",
port: int = 3000, port: int = 3000,
open_browser: bool = True, open_browser: bool = True,
@ -346,6 +347,7 @@ def start_ui(
6. Optionally open the browser 6. Optionally open the browser
Args: Args:
pid_callback: Callback to notify with PID of each spawned process
host: Host to bind the frontend server to (default: localhost) host: Host to bind the frontend server to (default: localhost)
port: Port to run the frontend server on (default: 3000) port: Port to run the frontend server on (default: 3000)
open_browser: Whether to open the browser automatically (default: True) open_browser: Whether to open the browser automatically (default: True)
@ -391,20 +393,19 @@ def start_ui(
"--port", "--port",
str(backend_port), str(backend_port),
], ],
stdout=subprocess.PIPE, # Inherit stdout/stderr from parent process to show logs
stderr=subprocess.PIPE, stdout=None,
text=True, stderr=None,
preexec_fn=os.setsid if hasattr(os, "setsid") else None, preexec_fn=os.setsid if hasattr(os, "setsid") else None,
) )
pid_callback(backend_process.pid)
# Give the backend a moment to start # Give the backend a moment to start
time.sleep(2) time.sleep(2)
if backend_process.poll() is not None: if backend_process.poll() is not None:
stdout, stderr = backend_process.communicate() logger.error("Backend server failed to start - process exited early")
logger.error("Backend server failed to start:")
logger.error(f"stdout: {stdout}")
logger.error(f"stderr: {stderr}")
return None return None
logger.info(f"✓ Backend API started at http://{backend_host}:{backend_port}") logger.info(f"✓ Backend API started at http://{backend_host}:{backend_port}")
@ -460,7 +461,7 @@ def start_ui(
logger.info("This may take a moment to compile and start...") logger.info("This may take a moment to compile and start...")
try: try:
# Use process group to ensure all child processes get terminated together # Create frontend in its own process group for clean termination
process = subprocess.Popen( process = subprocess.Popen(
["npm", "run", "dev"], ["npm", "run", "dev"],
cwd=frontend_path, cwd=frontend_path,
@ -468,11 +469,11 @@ def start_ui(
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, text=True,
preexec_fn=os.setsid preexec_fn=os.setsid if hasattr(os, "setsid") else None,
if hasattr(os, "setsid")
else None, # Create new process group on Unix
) )
pid_callback(process.pid)
# Give it a moment to start up # Give it a moment to start up
time.sleep(3) time.sleep(3)

View file

@ -174,30 +174,23 @@ def main() -> int:
# Handle UI flag # Handle UI flag
if hasattr(args, "start_ui") and args.start_ui: if hasattr(args, "start_ui") and args.start_ui:
server_process = None spawned_pids = []
def signal_handler(signum, frame): def signal_handler(signum, frame):
"""Handle Ctrl+C and other termination signals""" """Handle Ctrl+C and other termination signals"""
nonlocal server_process nonlocal spawned_pids
fmt.echo("\nShutting down UI server...") fmt.echo("\nShutting down UI server...")
if server_process:
for pid in spawned_pids:
try: try:
# Try graceful termination first pgid = os.getpgid(pid)
server_process.terminate() os.killpg(pgid, signal.SIGTERM)
try: fmt.success(f"✓ Process group {pgid} (PID {pid}) terminated.")
server_process.wait(timeout=5) except (OSError, ProcessLookupError) as e:
fmt.success("UI server stopped gracefully.") fmt.warning(f"Could not terminate process {pid}: {e}")
except subprocess.TimeoutExpired:
# If graceful termination fails, force kill
fmt.echo("Force stopping UI server...")
server_process.kill()
server_process.wait()
fmt.success("UI server stopped.")
except Exception as e:
fmt.warning(f"Error stopping server: {e}")
sys.exit(0) sys.exit(0)
# Set up signal handlers
signal.signal(signal.SIGINT, signal_handler) # Ctrl+C signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
signal.signal(signal.SIGTERM, signal_handler) # Termination request signal.signal(signal.SIGTERM, signal_handler) # Termination request
@ -206,8 +199,18 @@ def main() -> int:
fmt.echo("Starting cognee UI...") fmt.echo("Starting cognee UI...")
# Callback to capture PIDs of all spawned processes
def pid_callback(pid):
nonlocal spawned_pids
spawned_pids.append(pid)
server_process = start_ui( server_process = start_ui(
host="localhost", port=3000, open_browser=True, start_backend=True host="localhost",
port=3000,
open_browser=True,
start_backend=True,
auto_download=True,
pid_callback=pid_callback,
) )
if server_process: if server_process:
@ -229,10 +232,12 @@ def main() -> int:
return 0 return 0
else: else:
fmt.error("Failed to start UI server. Check the logs above for details.") fmt.error("Failed to start UI server. Check the logs above for details.")
signal_handler(signal.SIGTERM, None)
return 1 return 1
except Exception as ex: except Exception as ex:
fmt.error(f"Error starting UI: {str(ex)}") fmt.error(f"Error starting UI: {str(ex)}")
signal_handler(signal.SIGTERM, None)
if debug.is_debug_enabled(): if debug.is_debug_enabled():
raise ex raise ex
return 1 return 1