Fix folder watcher matching, notifications, and BLM venv isolation

- Remove status filter from folder watcher so tasks in any open status
  (including "internal review") are matched by keyword
- Add retry logic for stuck processing/blocked/unmatched KV states
- Fix notification ordering (newest first, limit 50) and date parsing
- Use BLM's own .venv Python instead of uv run for subprocess calls
- Document external tools venv convention in CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cora-start
PeninsulaInd 2026-02-20 13:35:54 -06:00
parent 3b4a8e47be
commit 62186d8dec
5 changed files with 23 additions and 7 deletions

View File

@ -109,6 +109,7 @@ uv add --group test <package>
- **Notifications**: All scheduler events go through `NotificationBus.push()`, never directly to a UI - **Notifications**: All scheduler events go through `NotificationBus.push()`, never directly to a UI
- **Tests**: Use `respx` to mock httpx calls, `tmp_db` fixture for isolated SQLite instances - **Tests**: Use `respx` to mock httpx calls, `tmp_db` fixture for isolated SQLite instances
- **ClickUp attachments**: `ClickUpClient.upload_attachment()` uses module-level `httpx.post()` (not the shared client) for multipart uploads - **ClickUp attachments**: `ClickUpClient.upload_attachment()` uses module-level `httpx.post()` (not the shared client) for multipart uploads
- **External tools**: Subprocess calls to external projects (e.g. Big-Link-Man) must use the tool's own `.venv` Python, never `uv run` from CheddahBot's environment. Resolve the venv python at `{tool_dir}/.venv/Scripts/python.exe` (Windows) or `{tool_dir}/.venv/bin/python` (Linux/Mac). Fail if no venv exists.
## Multi-Agent Configuration ## Multi-Agent Configuration

View File

@ -345,8 +345,11 @@ async def get_notifications():
if not _db: if not _db:
return {"notifications": []} return {"notifications": []}
notes = _db.get_notifications_after(0, limit=100) rows = _db._conn.execute(
return {"notifications": notes} "SELECT id, message, category, created_at FROM notifications"
" ORDER BY id DESC LIMIT 50"
).fetchall()
return {"notifications": [dict(r) for r in rows]}
@router.get("/system/kv-states") @router.get("/system/kv-states")

View File

@ -488,13 +488,16 @@ class Scheduler:
filename = xlsx_path.name filename = xlsx_path.name
kv_key = f"linkbuilding:watched:{filename}" kv_key = f"linkbuilding:watched:{filename}"
# Skip already processed files # Skip completed/failed; retry "processing" (killed run) and "blocked" (missing field)
existing = self.db.kv_get(kv_key) existing = self.db.kv_get(kv_key)
if existing: if existing:
try: try:
state = json.loads(existing) state = json.loads(existing)
if state.get("status") in ("completed", "processing", "failed"): if state.get("status") in ("completed", "failed"):
continue continue
if state.get("status") in ("processing", "blocked", "unmatched"):
log.info("Retrying '%s' state for %s", state["status"], filename)
self.db.kv_delete(kv_key)
except json.JSONDecodeError: except json.JSONDecodeError:
continue continue
@ -672,7 +675,7 @@ class Scheduler:
return None return None
try: try:
tasks = client.get_tasks_from_space(space_id, statuses=["to do"]) tasks = client.get_tasks_from_overall_lists(space_id)
except Exception as e: except Exception as e:
log.warning("ClickUp query failed in _match_xlsx_to_clickup: %s", e) log.warning("ClickUp query failed in _match_xlsx_to_clickup: %s", e)
return None return None

View File

@ -38,7 +38,16 @@ def _run_blm_command(
Always injects -u/-p from BLM_USERNAME/BLM_PASSWORD env vars. Always injects -u/-p from BLM_USERNAME/BLM_PASSWORD env vars.
""" """
cmd = ["uv", "run", "python", "main.py", *args] # Use BLM's own venv Python so its dependencies are available
venv_python = Path(blm_dir) / ".venv" / "Scripts" / "python.exe"
if not venv_python.exists():
# Fallback for Linux/Mac
venv_python = Path(blm_dir) / ".venv" / "bin" / "python"
if not venv_python.exists():
raise FileNotFoundError(
f"No .venv found in {blm_dir}. External tools must have their own venv."
)
cmd = [str(venv_python), "main.py", *args]
# Inject credentials from env vars # Inject credentials from env vars
username = os.getenv("BLM_USERNAME", "") username = os.getenv("BLM_USERNAME", "")

View File

@ -888,7 +888,7 @@ async function loadNotifications() {
let html = '<ul class="log-list">'; let html = '<ul class="log-list">';
notes.slice(0, 100).forEach(n => { notes.slice(0, 100).forEach(n => {
const time = n.created_at ? new Date(n.created_at * 1000).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }) : ''; const time = n.created_at ? new Date(n.created_at).toLocaleString('en-US', { month:'short', day:'numeric', hour:'2-digit', minute:'2-digit' }) : '';
const level = (n.level || 'info').toLowerCase(); const level = (n.level || 'info').toLowerCase();
const typeCls = level === 'error' ? 'error' : level === 'warning' ? 'error' : 'heartbeat'; const typeCls = level === 'error' ? 'error' : level === 'warning' ? 'error' : 'heartbeat';
html += `<li class="log-item"> html += `<li class="log-item">