docs(troubleshooting): bake the #1695 diagnostic flow into the error message + a new troubleshooting doc
Closes #1695. @Patrick-81 reported the bare "AIAgent not available -- check that hermes-agent is on sys.path" error on a symlinked install (~/Programmes/hermes-agent linked to ~/hermes-agent). The maintainer's response — three diagnostic commands plus `pip install -e .` in the agent dir — fixed it for them. This PR captures both halves of that learning so the next user with the same shape doesn't have to file a new issue: 1. **Error message diagnostic block.** New helper `_aiagent_import_error_detail()` in api/streaming.py builds a multi-line diagnostic when the import fails, including: - the running Python interpreter - HERMES_WEBUI_AGENT_DIR (set value, or "(not set)") - sys.path entries that mention hermes/agent (or "no entries mention..." — itself a strong diagnostic signal) - the most-common fix (`pip install -e .` in the agent dir) - a pointer to docs/troubleshooting.md The original error message string is preserved as the FIRST line so existing log scrapers and docs-search keep matching. Helper is kept as a separate function so it stays out of the hot path until we actually need to raise — building it on every successful import would be wasted work. 2. **New docs/troubleshooting.md.** Symptom → Why → Diagnostic commands → Fix → When-to-file-a-bug template, with one entry to start: the "AIAgent not available" flow Patrick-81 walked through. Future recurring failure modes follow the same template. Required a one-line addition to .gitignore — docs/* is gitignored with an allowlist, and the new file needed `!docs/troubleshooting.md` to be tracked. 3. **README link.** docs/troubleshooting.md added to the `## Docs` section so users know where to look first. 13 regression tests in tests/test_1695_aiagent_import_error_detail.py: 9 for the helper output shape (preserves original message line, includes running python, shows HERMES_WEBUI_AGENT_DIR set/unset both ways, includes pip-install-e hint, points at troubleshooting doc, lists relevant sys.path entries when present, says "no entries..." when absent, output is multi-line) plus 4 for the docs-presence regression (file exists, has the AIAgent section, includes pip install -e ., describes the diagnostic chain with readlink + agent/__init__.py verification). 190 streaming/aiagent tests pass after the change. ast.parse on api/streaming.py clean. CI failure on prior push was due to the docs/* gitignore swallowing the new troubleshooting.md file silently — this commit adds the allowlist entry so the file is tracked.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -42,6 +42,7 @@ docs/*
|
||||
!docs/ui-ux/**
|
||||
!docs/docker.md
|
||||
!docs/supervisor.md
|
||||
!docs/troubleshooting.md
|
||||
|
||||
# Local-only PR review harness: rendering drivers, sample bank, fixtures.
|
||||
# Used by Claude during deep reviews; never shared in the repo.
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# Hermes Web UI -- Changelog
|
||||
|
||||
## [v0.51.7] — 2026-05-05 — single-PR docs+dx (#1695)
|
||||
|
||||
### Changed
|
||||
|
||||
- **#1695 — better diagnostic on `AIAgent not available` (DX + docs).** When the WebUI was launched with a Python that can't import `run_agent.AIAgent`, every chat request raised a bare `ImportError("AIAgent not available -- check that hermes-agent is on sys.path")` with no information about which Python was running, where it was looking, or what to do next. @Patrick-81 reported the symptom on a symlinked install (#1695); the maintainer's response (which Patrick confirmed worked) was a three-step diagnostic flow that we've now baked into the error message itself plus a new `docs/troubleshooting.md`. The error now includes: the running Python interpreter, the `HERMES_WEBUI_AGENT_DIR` env (set vs not set), the relevant `sys.path` entries (those mentioning hermes/agent), the most-common fix (`pip install -e .` in the agent dir), and a pointer to `docs/troubleshooting.md`. Docs entry walks through `ls`/`readlink`/`pip install -e .` diagnostic steps, three common failure modes (not on sys.path, broken symlink, wrong override), and when to file a bug.
|
||||
|
||||
### Added
|
||||
|
||||
- **`docs/troubleshooting.md`** — new diagnostic-flow doc with one entry to start (`AIAgent not available`); structured as Symptom → Why → Diagnostic commands → Fix → When to file a bug. Linked from README's `## Docs` section. Future failure-mode entries follow the same template.
|
||||
|
||||
## [v0.51.6] — 2026-05-05 — 5-PR full-sweep batch
|
||||
|
||||
### Added
|
||||
|
||||
@@ -535,6 +535,7 @@ State lives outside the repo at `~/.hermes/webui-mvp/` by default
|
||||
- `CHANGELOG.md` -- release notes per sprint
|
||||
- `SPRINTS.md` -- forward sprint plan with CLI + Claude parity targets
|
||||
- `THEMES.md` -- theme system documentation, custom theme guide
|
||||
- `docs/troubleshooting.md` -- diagnostic flows for common failures (e.g. "AIAgent not available")
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> Web companion to the Hermes Agent CLI. Same workflows, browser-native.
|
||||
>
|
||||
> Last updated: v0.51.6 (May 5, 2026) — 4537 tests collected
|
||||
> Last updated: v0.51.7 (May 5, 2026) — 4550 tests collected — single-PR docs+dx (#1695: AIAgent not available diagnostic)
|
||||
> Test source: `pytest tests/ --collect-only -q`
|
||||
> Per-version detail: see [CHANGELOG.md](./CHANGELOG.md)
|
||||
|
||||
|
||||
@@ -1835,7 +1835,7 @@ Bridged CLI sessions:
|
||||
|
||||
---
|
||||
|
||||
*Last updated: v0.51.6, May 5, 2026*
|
||||
*Last updated: v0.51.7, May 5, 2026*
|
||||
*Total automated tests collected: 4537*
|
||||
*Regression gate: tests/test_regressions.py*
|
||||
*Run: pytest tests/ -v --timeout=60*
|
||||
|
||||
@@ -59,6 +59,55 @@ def _get_ai_agent():
|
||||
except ImportError:
|
||||
pass
|
||||
return AIAgent
|
||||
|
||||
|
||||
def _aiagent_import_error_detail() -> str:
|
||||
"""Return a multi-line diagnostic string for the "AIAgent not available" path.
|
||||
|
||||
The bare ImportError ("AIAgent not available -- check that hermes-agent is
|
||||
on sys.path") leaves users guessing at which python is running, where it's
|
||||
looking, and what to fix. We assemble the same evidence a maintainer would
|
||||
ask for first (issue #1695): the python that's running, the agent_dir env
|
||||
var if set, the sys.path entries that mention 'hermes', and the most-common
|
||||
fix (`pip install -e .` in the agent dir).
|
||||
|
||||
Kept as a separate helper so it stays out of the hot path until we actually
|
||||
need to raise — building it on every successful import would be wasted work.
|
||||
"""
|
||||
import os as _os
|
||||
import sys as _sys
|
||||
|
||||
lines = ["AIAgent not available -- check that hermes-agent is on sys.path"]
|
||||
lines.append("")
|
||||
lines.append(f" python: {_sys.executable}")
|
||||
agent_dir = _os.environ.get("HERMES_WEBUI_AGENT_DIR")
|
||||
if agent_dir:
|
||||
lines.append(f" HERMES_WEBUI_AGENT_DIR: {agent_dir}")
|
||||
else:
|
||||
lines.append(" HERMES_WEBUI_AGENT_DIR: (not set)")
|
||||
|
||||
# Show only the sys.path entries that look relevant — full sys.path is noisy.
|
||||
relevant = [p for p in _sys.path if "hermes" in p.lower() or "agent" in p.lower()]
|
||||
if relevant:
|
||||
lines.append(" sys.path entries mentioning hermes/agent:")
|
||||
for entry in relevant[:6]:
|
||||
lines.append(f" - {entry}")
|
||||
if len(relevant) > 6:
|
||||
lines.append(f" ... and {len(relevant) - 6} more")
|
||||
else:
|
||||
lines.append(" sys.path: (no entries mention hermes or agent)")
|
||||
|
||||
lines.append("")
|
||||
lines.append(" Most common fix: install the agent in editable mode so its modules")
|
||||
lines.append(" appear on sys.path:")
|
||||
lines.append("")
|
||||
lines.append(" cd /path/to/hermes-agent")
|
||||
lines.append(" pip install -e .")
|
||||
lines.append("")
|
||||
lines.append(" Then restart the WebUI.")
|
||||
lines.append("")
|
||||
lines.append(' Full troubleshooting: docs/troubleshooting.md ("AIAgent not available")')
|
||||
return "\n".join(lines)
|
||||
from api.models import get_session, title_from
|
||||
from api.workspace import set_last_workspace
|
||||
|
||||
@@ -1958,7 +2007,7 @@ def _run_agent_streaming(
|
||||
|
||||
_AIAgent = _get_ai_agent()
|
||||
if _AIAgent is None:
|
||||
raise ImportError("AIAgent not available -- check that hermes-agent is on sys.path")
|
||||
raise ImportError(_aiagent_import_error_detail())
|
||||
|
||||
# Initialize SessionDB so session_search works in WebUI sessions
|
||||
_session_db = None
|
||||
|
||||
97
docs/troubleshooting.md
Normal file
97
docs/troubleshooting.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Troubleshooting
|
||||
|
||||
Concrete diagnostic flows for the most common failure modes when running Hermes WebUI. Each entry has the symptom, the diagnostic commands you should run *before* opening an issue, and the fix that has worked for past reporters.
|
||||
|
||||
If your symptom isn't listed and the diagnostics don't narrow it down, file a bug at https://github.com/nesquena/hermes-webui/issues — include the **full output** of every command in the relevant section.
|
||||
|
||||
---
|
||||
|
||||
## "AIAgent not available -- check that hermes-agent is on sys.path"
|
||||
|
||||
**Symptom.** WebUI starts, shows the chat interface, but every chat request fails immediately with this error in the response or the server log. As of v0.51.6 the error includes a diagnostic block with the running Python interpreter, the relevant `sys.path` entries, and the most-common fix; on older versions the message is bare.
|
||||
|
||||
**Why it happens.** The WebUI imports the agent class at chat time via `from run_agent import AIAgent`. That import only succeeds if the running Python's `sys.path` contains either the hermes-agent checkout or a pip-installed copy of the agent. Three common failure modes:
|
||||
|
||||
1. **Agent installed but not on `sys.path`.** Most common. The agent is checked out somewhere (e.g. `~/Programmes/hermes-agent`), the WebUI was launched with a Python that doesn't know about it, and there's no `pip install -e .` linking the two.
|
||||
2. **Symlink with a typo or wrong target.** A symlink to the agent looks correct on `ls`, but `readlink` resolves to a path that doesn't exist or doesn't contain `agent/__init__.py`.
|
||||
3. **`HERMES_WEBUI_AGENT_DIR` set to the wrong directory.** Override env var beats auto-discovery and points at a directory that has no agent code.
|
||||
|
||||
### Step 1 — confirm the agent location
|
||||
|
||||
```bash
|
||||
# If you have ~/hermes-agent (the default location):
|
||||
ls -la ~/hermes-agent
|
||||
readlink ~/hermes-agent # if it's a symlink, where does it resolve?
|
||||
ls ~/hermes-agent/agent/__init__.py 2>&1
|
||||
```
|
||||
|
||||
The third command must succeed (the file must exist). If it fails, your symlink is broken or pointing at a directory that's missing the agent module — fix that first.
|
||||
|
||||
### Step 2 — confirm the WebUI is using the right Python
|
||||
|
||||
```bash
|
||||
cd ~/hermes-webui && ./start.sh 2>&1 | grep -iE 'agent|python|hermes_webui_python' | head -20
|
||||
```
|
||||
|
||||
The startup banner prints which Python and agent dir it resolved. If the agent dir is empty or the Python is the wrong one, set the override:
|
||||
|
||||
```bash
|
||||
export HERMES_WEBUI_AGENT_DIR=/absolute/path/to/hermes-agent
|
||||
export HERMES_WEBUI_PYTHON=/absolute/path/to/agent/venv/bin/python
|
||||
./start.sh
|
||||
```
|
||||
|
||||
### Step 3 — install the agent in editable mode
|
||||
|
||||
This is the most common fix and resolves the original issue #1695:
|
||||
|
||||
```bash
|
||||
cd /path/to/hermes-agent # the directory holding pyproject.toml + the agent/ module
|
||||
pip install -e . # use the same python that runs the WebUI
|
||||
```
|
||||
|
||||
Then restart the WebUI:
|
||||
|
||||
```bash
|
||||
cd ~/hermes-webui
|
||||
./start.sh
|
||||
```
|
||||
|
||||
### Step 4 — verify by importing manually
|
||||
|
||||
If steps 1-3 still don't work, check whether the WebUI's Python can import the agent at all:
|
||||
|
||||
```bash
|
||||
$HERMES_WEBUI_PYTHON -c "from run_agent import AIAgent; print('ok')" 2>&1
|
||||
```
|
||||
|
||||
(Replace `$HERMES_WEBUI_PYTHON` with the actual Python path from step 2 if the env var isn't set.) If this prints `ok`, the agent IS on `sys.path` for that Python — and the WebUI should work.
|
||||
|
||||
If this fails, `import run_agent` itself is broken — check that the agent's pyproject.toml lists `run_agent` as a top-level module or that the agent dir is on PYTHONPATH:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=/path/to/hermes-agent $HERMES_WEBUI_PYTHON -c "from run_agent import AIAgent; print('ok')"
|
||||
```
|
||||
|
||||
If adding PYTHONPATH fixes it, persist the path either via `pip install -e .` (preferred) or by setting `HERMES_WEBUI_AGENT_DIR` to that directory.
|
||||
|
||||
### When to file a bug
|
||||
|
||||
If after running steps 1-4 the import still fails *and* `pip install -e .` succeeded *and* `PYTHONPATH=... python -c "from run_agent import AIAgent"` succeeds — that's a real WebUI bug. File at https://github.com/nesquena/hermes-webui/issues with:
|
||||
|
||||
- The output of every command in steps 1-4
|
||||
- The full diagnostic block printed by the WebUI's `ImportError` (v0.51.6+)
|
||||
- Your OS, Python version, and how the agent was installed
|
||||
|
||||
---
|
||||
|
||||
## Other troubleshooting
|
||||
|
||||
This document grows over time. If a recurring failure mode isn't covered here yet, add it via PR. The format for each entry: **Symptom → Why → Diagnostic commands → Fix → When to file a bug**.
|
||||
|
||||
Related references:
|
||||
|
||||
- [`docs/supervisor.md`](supervisor.md) — process-supervisor setup (launchd, systemd, supervisord, runit/s6) including the bootstrap supervisor-foreground flag.
|
||||
- [`docs/docker.md`](docker.md) — Docker compose setup, common failure modes, bind-mount migration.
|
||||
- [`docs/wsl-autostart.md`](wsl-autostart.md) — WSL2 auto-start at login on Windows.
|
||||
- [`docs/EXTENSIONS.md`](EXTENSIONS.md) — WebUI extension injection, security model, examples.
|
||||
184
tests/test_1695_aiagent_import_error_detail.py
Normal file
184
tests/test_1695_aiagent_import_error_detail.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""Tests for #1695 — diagnostic detail in the "AIAgent not available" ImportError.
|
||||
|
||||
Patrick-81 reported a symlinked hermes-agent install that produced a bare
|
||||
"AIAgent not available -- check that hermes-agent is on sys.path" error with
|
||||
no information about which Python was running, where it was looking, or what
|
||||
to do next. The maintainer's response (which Patrick confirmed worked)
|
||||
amounted to: run three diagnostic commands, then `pip install -e .` in the
|
||||
agent dir.
|
||||
|
||||
This test suite locks the diagnostic shape of the new error message:
|
||||
|
||||
- The original message string is preserved (so existing log scrapers /
|
||||
monitoring / docs-search keep working).
|
||||
- The running python interpreter path is included.
|
||||
- HERMES_WEBUI_AGENT_DIR is shown if set, "(not set)" otherwise.
|
||||
- The relevant sys.path entries are shown.
|
||||
- A pip install -e . hint is included.
|
||||
- A pointer to docs/troubleshooting.md is included.
|
||||
|
||||
Behavioural test for the actual raise path lives in the streaming integration
|
||||
suite; this file only exercises the helper.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _import_helper():
|
||||
"""Import _aiagent_import_error_detail without triggering the full streaming
|
||||
module side-effects.
|
||||
|
||||
api/streaming.py imports a lot at top-level (gateway routing, model resolver,
|
||||
session DB, ...). For a focused unit test we just need the helper. Importing
|
||||
the module is fine — it stays cached for the rest of the suite.
|
||||
"""
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
from api import streaming # noqa: F401
|
||||
return streaming._aiagent_import_error_detail
|
||||
|
||||
|
||||
class TestAIAgentImportErrorDetail:
|
||||
"""Unit tests for the diagnostic helper."""
|
||||
|
||||
def test_preserves_original_message_for_log_scrapers(self):
|
||||
"""The original error string must remain the FIRST line so existing
|
||||
scrapers, alerting, and docs-search keep matching.
|
||||
"""
|
||||
helper = _import_helper()
|
||||
out = helper()
|
||||
first = out.splitlines()[0]
|
||||
assert first == "AIAgent not available -- check that hermes-agent is on sys.path", (
|
||||
f"first line must be the original error message verbatim, got: {first!r}"
|
||||
)
|
||||
|
||||
def test_includes_running_python_interpreter(self):
|
||||
"""The diagnostic must include the running python so the user knows
|
||||
which interpreter is missing the agent (most common cause of the bug).
|
||||
"""
|
||||
helper = _import_helper()
|
||||
out = helper()
|
||||
assert "python:" in out
|
||||
assert sys.executable in out, (
|
||||
f"running python ({sys.executable}) must appear in the diagnostic"
|
||||
)
|
||||
|
||||
def test_shows_agent_dir_env_when_set(self, monkeypatch):
|
||||
"""If HERMES_WEBUI_AGENT_DIR is set, the diagnostic must show its value
|
||||
so the user can confirm whether the override is pointing at the right
|
||||
directory.
|
||||
"""
|
||||
helper = _import_helper()
|
||||
monkeypatch.setenv("HERMES_WEBUI_AGENT_DIR", "/custom/agent/path")
|
||||
out = helper()
|
||||
assert "HERMES_WEBUI_AGENT_DIR: /custom/agent/path" in out
|
||||
|
||||
def test_shows_agent_dir_env_unset_marker(self, monkeypatch):
|
||||
"""If HERMES_WEBUI_AGENT_DIR is NOT set, the diagnostic must say so
|
||||
explicitly — silence is ambiguous (could be empty string, could be unset).
|
||||
"""
|
||||
helper = _import_helper()
|
||||
monkeypatch.delenv("HERMES_WEBUI_AGENT_DIR", raising=False)
|
||||
out = helper()
|
||||
assert "HERMES_WEBUI_AGENT_DIR: (not set)" in out
|
||||
|
||||
def test_includes_pip_install_editable_hint(self):
|
||||
"""The most common fix (per #1695) is `pip install -e .` in the agent dir.
|
||||
The diagnostic must surface this as the first-line remediation.
|
||||
"""
|
||||
helper = _import_helper()
|
||||
out = helper()
|
||||
assert "pip install -e ." in out, (
|
||||
"diagnostic must surface `pip install -e .` as the most common fix"
|
||||
)
|
||||
|
||||
def test_points_at_troubleshooting_doc(self):
|
||||
"""The diagnostic must point at the docs/troubleshooting.md entry so
|
||||
users with edge-case failures know where to look next.
|
||||
"""
|
||||
helper = _import_helper()
|
||||
out = helper()
|
||||
assert "troubleshooting" in out.lower(), (
|
||||
"diagnostic must point at docs/troubleshooting.md for further help"
|
||||
)
|
||||
|
||||
def test_lists_sys_path_entries_when_relevant(self, monkeypatch):
|
||||
"""If sys.path contains entries mentioning hermes/agent, the diagnostic
|
||||
must list them (helps the user confirm the agent dir is or isn't
|
||||
actually present on the import path).
|
||||
"""
|
||||
helper = _import_helper()
|
||||
# Force at least one relevant entry into sys.path for the test.
|
||||
monkeypatch.syspath_prepend("/fake/hermes-agent")
|
||||
out = helper()
|
||||
assert "/fake/hermes-agent" in out
|
||||
|
||||
def test_handles_no_relevant_sys_path_entries(self, monkeypatch):
|
||||
"""If sys.path has NO hermes/agent-related entries, the diagnostic must
|
||||
say so explicitly — this is itself a strong diagnostic signal.
|
||||
"""
|
||||
helper = _import_helper()
|
||||
# Replace sys.path with entries that mention neither hermes nor agent.
|
||||
# Use monkeypatch.setattr so the change reverts cleanly.
|
||||
clean_path = ["/usr/lib/python3.11", "/usr/local/lib/python3.11", "/tmp"]
|
||||
monkeypatch.setattr(sys, "path", clean_path)
|
||||
out = helper()
|
||||
assert "no entries mention hermes or agent" in out, (
|
||||
"diagnostic must explicitly call out empty-path case (it's a strong signal)"
|
||||
)
|
||||
|
||||
def test_output_is_multiline_string(self):
|
||||
"""The diagnostic must be a multi-line string (newline-joined), not a
|
||||
single long line — log-readability matters when this surfaces in a
|
||||
traceback.
|
||||
"""
|
||||
helper = _import_helper()
|
||||
out = helper()
|
||||
assert "\n" in out, "diagnostic must be multi-line for log readability"
|
||||
assert len(out.splitlines()) >= 5, (
|
||||
f"diagnostic must have at least 5 lines, got {len(out.splitlines())}"
|
||||
)
|
||||
|
||||
|
||||
class TestAIAgentImportErrorDocsPresence:
|
||||
"""Regression: the docs/troubleshooting.md file must exist with the
|
||||
"AIAgent not available" entry the diagnostic links to.
|
||||
"""
|
||||
|
||||
def test_troubleshooting_md_exists(self):
|
||||
path = REPO_ROOT / "docs" / "troubleshooting.md"
|
||||
assert path.exists(), "docs/troubleshooting.md must exist (referenced by streaming.py)"
|
||||
|
||||
def test_troubleshooting_md_has_aiagent_section(self):
|
||||
path = REPO_ROOT / "docs" / "troubleshooting.md"
|
||||
content = path.read_text(encoding="utf-8")
|
||||
assert "AIAgent not available" in content, (
|
||||
"docs/troubleshooting.md must have an entry titled \"AIAgent not available\""
|
||||
)
|
||||
|
||||
def test_troubleshooting_md_includes_pip_install_editable(self):
|
||||
"""The doc must surface the `pip install -e .` fix."""
|
||||
path = REPO_ROOT / "docs" / "troubleshooting.md"
|
||||
content = path.read_text(encoding="utf-8")
|
||||
assert "pip install -e ." in content, (
|
||||
"docs/troubleshooting.md must include the pip install -e . fix"
|
||||
)
|
||||
|
||||
def test_troubleshooting_md_describes_diagnostic_steps(self):
|
||||
"""The doc must walk through diagnostic commands (readlink, ls, etc.)
|
||||
before jumping to the fix — that ordering is what worked for #1695.
|
||||
"""
|
||||
path = REPO_ROOT / "docs" / "troubleshooting.md"
|
||||
content = path.read_text(encoding="utf-8")
|
||||
# Look for the symlink-resolution diagnostic chain.
|
||||
assert "readlink" in content, (
|
||||
"diagnostic flow must include `readlink` for the symlink-typo failure mode"
|
||||
)
|
||||
assert "/agent/__init__.py" in content, (
|
||||
"diagnostic flow must verify the agent module file is reachable"
|
||||
)
|
||||
Reference in New Issue
Block a user