OpenAdaptAI / OpenAdapt

AI-First Process Automation with Large ([Language (LLMs) / Action (LAMs) / Multimodal (LMMs)] / Visual Language (VLMs)) Models
https://www.OpenAdapt.AI
MIT License
743 stars 98 forks source link

[Bug]: `python -m openadapt.app.dashboard.run` broken #718

Open abrichr opened 4 weeks ago

abrichr commented 4 weeks ago

Describe the bug

(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % poetry run install-dashboard
N/A: version "N/A -> N/A" is not yet installed.

You need to run "nvm install N/A" to install it before using it.
v21.7.3 is already installed.
Now using node v21.7.3 (npm v10.5.0)
Found '/Users/abrichr/oa/OpenAdapt/openadapt/app/dashboard/.nvmrc' with version <21>
Now using node v21.7.3 (npm v10.5.0)

added 1 package, and audited 460 packages in 2s

173 packages are looking for funding
  run `npm fund` for details

3 moderate severity vulnerabilities

To address issues that do not require attention, run:
  npm audit fix

To address all issues, run:
  npm audit fix --force

Run `npm audit` for details.
npm notice 
npm notice New minor version of npm available! 10.5.0 -> 10.8.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.8.1
npm notice Run npm install -g npm@10.8.1 to update!
npm notice 
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % python -m openadapt.app.dashboard.run
Exception in thread Thread-1 (run_client):
Traceback (most recent call last):
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/abrichr/oa/OpenAdapt/openadapt/app/dashboard/run.py", line 35, in run_client
    dashboard_process = subprocess.Popen(
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 971, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 1863, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'node'
^C2024-06-06 16:49:01.663 | DEBUG    | __main__:cleanup:59 - Terminating the dashboard client.
2024-06-06 16:49:01.664 | DEBUG    | __main__:cleanup:64 - Dashboard client terminated.
^C
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % 

To Reproduce

See above

abrichr commented 4 weeks ago

As part of fixing this, please also add a test 🙏

KIRA009 commented 4 weeks ago

Hi @abrichr are you trying this on a new system? Could you check if running node in the same terminal raises any errors. As far as I understand, the node exectuable is not where it should be (in one of the bin directories added to PATH)

abrichr commented 3 weeks ago

@KIRA009

(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % python -m openadapt.app.dashboard.run
Exception in thread Thread-1 (run_client):
Traceback (most recent call last):
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/abrichr/oa/OpenAdapt/openadapt/app/dashboard/run.py", line 35, in run_client
    dashboard_process = subprocess.Popen(
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 971, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 1863, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'node'
^C2024-06-08 23:43:40.067 | DEBUG    | __main__:cleanup:59 - Terminating the dashboard client.
2024-06-08 23:43:40.068 | DEBUG    | __main__:cleanup:64 - Dashboard client terminated.
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % node
zsh: command not found: node
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % poetry run install-dashboard
N/A: version "N/A -> N/A" is not yet installed.

You need to run "nvm install N/A" to install it before using it.
v21.7.3 is already installed.
Now using node v21.7.3 (npm v10.5.0)
Found '/Users/abrichr/oa/OpenAdapt/openadapt/app/dashboard/.nvmrc' with version <21>
Now using node v21.7.3 (npm v10.5.0)

up to date, audited 460 packages in 1s

173 packages are looking for funding
  run `npm fund` for details

3 moderate severity vulnerabilities

To address issues that do not require attention, run:
  npm audit fix

To address all issues, run:
  npm audit fix --force

Run `npm audit` for details.
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % node
zsh: command not found: node
abrichr commented 3 weeks ago

I tried manually running nvm use 21 then poetry install-dashboard:

(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % poetry run install-dashboard                                                                                                                                     
v21.7.3 is already installed.                                                                              
Now using node v21.7.3 (npm v10.5.0)                                                                                                                                                                                  
No .nvmrc file found                                                                                                                                                                                                  

Node Version Manager                                                                                                                                                                                                  

Note: <version> refers to any version-like string nvm understands. This includes:                                                                                                                                     
  - full or partial version numbers, starting with an optional "v" (0.10, v0.1.2, v1)                                                                                                                                 
  - default (built-in) aliases: node, stable, unstable, iojs, system                                                                                                                                                  
  - custom aliases you define with `nvm alias foo`                                                         

 Any options that produce colorized output should respect the `--no-colors` option.                                                                                                                                   

Usage:                                                                                                                                                                                                                
  nvm --help                                Show this message        
...

Any suggestions? 🙏

abrichr commented 3 weeks ago

ChatGPT:

In the provided run.py script, the main problem is related to the subprocess not finding the node executable, as seen from the error message FileNotFoundError: [Errno 2] No such file or directory: 'node'. This is typically because the environment in which Python's subprocess.Popen is running does not have the correct paths to node, which should be set by nvm.

Here’s how you can modify run.py to dynamically include the correct path to node in the subprocess environment:

  1. Load the correct node environment: Before starting the subprocess, ensure that nvm is sourced and the correct version of node is used. This setup needs to be done within the same context as the subprocess.Popen command because environment settings in a shell script (like entrypoint.sh) or in a different subprocess do not persist across separate subprocess invocations.

  2. Modify run_client(): You can modify the run_client() function to dynamically set the environment for the subprocess. This involves sourcing nvm and adding the directory containing the node binary to the PATH of the subprocess environment.

Here’s a way to implement these changes:

from threading import Thread
import os
import pathlib
import subprocess
import webbrowser

from loguru import logger

from openadapt.build_utils import is_running_from_executable
from openadapt.config import POSTHOG_HOST, POSTHOG_PUBLIC_KEY, config

from .api.index import run_app

dashboard_process = None

def run() -> Thread:
    """Run the dashboard web application."""
    cur_dir = pathlib.Path(__file__).parent

    def run_client() -> subprocess.Popen:
        """The entry point for the thread that runs the dashboard client."""
        if is_running_from_executable():
            webbrowser.open(
                f"http://localhost:{config.DASHBOARD_SERVER_PORT}/recordings"
            )
            run_app()
            return

        global dashboard_process

        # Load nvm and get node path
        nvm_path = os.path.expanduser('~/.nvm')
        nvm_init_script = os.path.join(nvm_path, 'nvm.sh')

        command = f'source {nvm_init_script} && nvm use 21 && which node'
        node_path = subprocess.check_output(command, shell=True, executable='/bin/bash').decode().strip()

        env = {
            **os.environ,
            "PATH": f"{os.path.dirname(node_path)}:{os.environ['PATH']}",
            "DASHBOARD_CLIENT_PORT": str(config.DASHBOARD_CLIENT_PORT),
            "DASHBOARD_SERVER_PORT": str(config.DASHBOARD_SERVER_PORT),
            "NEXT_PUBLIC_POSTHOG_HOST": POSTHOG_HOST,
            "NEXT_PUBLIC_POSTHOG_PUBLIC_KEY": POSTHOG_PUBLIC_KEY,
            "NEXT_PUBLIC_MODE": "production" if is_running_from_executable() else "development",
        }

        dashboard_process = subprocess.Popen(
            ["node", "index.js"],
            cwd=cur_dir,
            env=env,
        )

    return Thread(
        target=run_client,
        daemon=True,
    )

def cleanup() -> None:
    """Cleanup the dashboard web application process."""
    logger.debug("Terminating the dashboard client.")
    global dashboard_process
    if dashboard_process:
        dashboard_process.terminate()
        dashboard_process.wait()
    logger.debug("Dashboard client terminated.")

if __name__ == "__main__":
    dashboard_thread = run()
    dashboard_thread.start()
    try:
        while True:
            pass
    except KeyboardInterrupt:
        cleanup()

This implementation uses the subprocess.check_output to dynamically load nvm and get the path to the node binary, ensuring it is included in the subprocess's PATH. This way, your subprocess will correctly locate and execute node.

abrichr commented 3 weeks ago
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt %  python -m openadapt.app.dashboard.run                                                                                                                                               00:21:40 [5/324]
2024-06-09 00:20:35.979 | INFO     | __main__:run_client:36 - command='source /Users/abrichr/.nvm/nvm.sh && nvm use 21 && which node'                                                                                                                     
Exception in thread Thread-1 (run_client):                                                                                                                                                                                                                
Traceback (most recent call last):                                                                                                                                                                                                                        
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 1016, in _bootstrap_inner                                                                                                   
    self.run()                                                                                                                                                                                                                                            
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 953, in run                                                                                                                 
    self._target(*self._args, **self._kwargs)                                                                                                                                                                         
  File "/Users/abrichr/oa/OpenAdapt/openadapt/app/dashboard/run.py", line 37, in run_client                                                                                                                           
    node_path = subprocess.check_output(                                                                                                                                                                              
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 421, in check_output                                                                                                       
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,                   
  File "/opt/homebrew/Cellar/python@3.10/3.10.14/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 526, in run                                                                                                                
    raise CalledProcessError(retcode, process.args,                                                        
subprocess.CalledProcessError: Command 'source /Users/abrichr/.nvm/nvm.sh && nvm use 21 && which node' returned non-zero exit status 3.
^C2024-06-09 00:20:38.087 | DEBUG    | __main__:cleanup:67 - Terminating the dashboard client.                               
2024-06-09 00:20:38.088 | DEBUG    | __main__:cleanup:72 - Dashboard client terminated.                    
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % source /Users/abrichr/.nvm/nvm.sh && nvm use 21 && which node                                     
N/A: version "N/A -> N/A" is not yet installed.                                                            

You need to run "nvm install N/A" to install it before using it.                                                                                                                                                      
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % nvm install N/A                       
Version 'N/A' not found - try `nvm ls-remote` to browse available versions.                                                  
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % nv use 21                                                                                                                                                        
zsh: command not found: nv                                                                                                                                                                                            
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % nvm use 21                                                                                                                                                       
Now using node v21.7.3 (npm v10.5.0)               
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % which node                                                                                                                                                       
/Users/abrichr/.nvm/versions/node/v21.7.3/bin/node                                                         
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt % source /Users/abrichr/.nvm/nvm.sh && nvm use 21 && which node                                                                                                                                        
Now using node v21.7.3 (npm v10.5.0)                                                                                                                                                                                  
/Users/abrichr/.nvm/versions/node/v21.7.3/bin/node                                                         
(openadapt-py3.10) abrichr@MacBook-Pro-5 OpenAdapt %  python -m openadapt.app.dashboard.run                                                                                                                                                               
2024-06-09 00:21:38.120 | INFO     | __main__:run_client:36 - command='source /Users/abrichr/.nvm/nvm.sh && nvm use 21 && which node'                                                                                                                     

> nextjs-fastapi@0.1.0 dev                                                                                 
> concurrently "npm run next-dev" "npm run fastapi-dev"       

[1]                                                                                                        
[1] > nextjs-fastapi@0.1.0 fastapi-dev                                                                     
[1] > python3 -m uvicorn api.index:app --port $DASHBOARD_SERVER_PORT --reload                                                                                                                                         
[1]                                                                                                                                                                                                                   
[0]                                                                                                                                                                                                                   
[0] > nextjs-fastapi@0.1.0 next-dev         
[0] > next dev -p $DASHBOARD_CLIENT_PORT          
[0]                                                                                                                                                                                                                   
[1] INFO:     Will watch for changes in these directories: ['/Users/abrichr/oa/OpenAdapt/openadapt/app/dashboard']                   
[1] INFO:     Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit)                                                
[1] INFO:     Started reloader process [61914] using WatchFiles                                                              
[0]  ⚠ Specified "rewrites" will not automatically work with "output: export". See more info here: https://nextjs.org/docs/messages/export-no-custom-routes                                                                                               
[0]  ⚠ Specified "rewrites" will not automatically work with "output: export". See more info here: https://nextjs.org/docs/messages/export-no-custom-routes                                                                                               
[0]    ▲ Next.js 14.1.4                                       
[0]    - Local:        http://localhost:5173 

I'm not sure why this fixed it. @KIRA009 any thoughts on how we can detect this situation, and ideally fix it (or at least warn the user)?