Fix task looping: increase stale recovery to 6h, add file logging, use UNC paths

- Stale task recovery threshold 2h → 6h to prevent resetting tasks while Cora is still running
- Add rotating file logger (WARNING+) to logs/cheddahbot.log for debugging
- Silence httpx/httpcore INFO spam from terminal
- Switch watch folder paths from Z: drive letters to UNC paths to avoid intermittent mount drops
- Fix test_db tests to add messages so list_conversations includes them

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix/customer-field-migration
PeninsulaInd 2026-03-04 21:41:46 -06:00
parent 83c7c378e5
commit 45ab4c1b33
4 changed files with 36 additions and 10 deletions

View File

@ -1,6 +1,7 @@
"""Entry point: python -m cheddahbot""" """Entry point: python -m cheddahbot"""
import logging import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path from pathlib import Path
from .agent import Agent from .agent import Agent
@ -15,6 +16,22 @@ logging.basicConfig(
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
datefmt="%H:%M:%S", datefmt="%H:%M:%S",
) )
# Warnings and errors to rotating log file
_log_dir = Path(__file__).resolve().parent.parent / "logs"
_log_dir.mkdir(exist_ok=True)
_file_handler = RotatingFileHandler(
_log_dir / "cheddahbot.log", maxBytes=5 * 1024 * 1024, backupCount=5
)
_file_handler.setLevel(logging.WARNING)
_file_handler.setFormatter(
logging.Formatter("%(asctime)s [%(name)s] %(levelname)s: %(message)s")
)
logging.getLogger().addHandler(_file_handler)
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
log = logging.getLogger("cheddahbot") log = logging.getLogger("cheddahbot")

View File

@ -271,7 +271,7 @@ class Scheduler:
return self._clickup_client return self._clickup_client
# Maximum time a task can stay in "automation underway" before recovery (seconds) # Maximum time a task can stay in "automation underway" before recovery (seconds)
STALE_TASK_THRESHOLD_SECONDS = 2 * 60 * 60 # 2 hours STALE_TASK_THRESHOLD_SECONDS = 6 * 60 * 60 # 6 hours
def _clickup_loop(self): def _clickup_loop(self):
"""Poll ClickUp for tasks on a regular interval.""" """Poll ClickUp for tasks on a regular interval."""

View File

@ -92,7 +92,7 @@ clickup:
# Link Building settings # Link Building settings
link_building: link_building:
blm_dir: "E:/dev/Big-Link-Man" blm_dir: "E:/dev/Big-Link-Man"
watch_folder: "Z:/cora-inbox" watch_folder: "//PennQnap1/SHARE1/cora-inbox"
watch_interval_minutes: 60 watch_interval_minutes: 60
default_branded_plus_ratio: 0.7 default_branded_plus_ratio: 0.7
@ -104,12 +104,12 @@ autocora:
success_status: "running cora" success_status: "running cora"
error_status: "error" error_status: "error"
enabled: true enabled: true
cora_human_inbox: "Z:/Cora-For-Human" cora_human_inbox: "//PennQnap1/SHARE1/Cora-For-Human"
# Content creation settings # Content creation settings
content: content:
cora_inbox: "Z:/content-cora-inbox" cora_inbox: "//PennQnap1/SHARE1/content-cora-inbox"
outline_dir: "Z:/content-outlines" outline_dir: "//PennQnap1/SHARE1/content-outlines"
# ntfy.sh push notifications # ntfy.sh push notifications
ntfy: ntfy:

View File

@ -10,21 +10,27 @@ from cheddahbot.db import Database
class TestConversationsAgentName: class TestConversationsAgentName:
"""Conversations are tagged by agent_name for per-agent history filtering.""" """Conversations are tagged by agent_name for per-agent history filtering."""
def _add_msg(self, db, conv_id):
"""Add a dummy message so list_conversations() includes this conv."""
db.add_message(conv_id, "user", "hello")
def test_create_with_default_agent_name(self, tmp_db): def test_create_with_default_agent_name(self, tmp_db):
tmp_db.create_conversation("conv1") tmp_db.create_conversation("conv1")
self._add_msg(tmp_db, "conv1")
convs = tmp_db.list_conversations() convs = tmp_db.list_conversations()
assert len(convs) == 1 assert len(convs) == 1
assert convs[0]["agent_name"] == "default" assert convs[0]["agent_name"] == "default"
def test_create_with_custom_agent_name(self, tmp_db): def test_create_with_custom_agent_name(self, tmp_db):
tmp_db.create_conversation("conv1", agent_name="writer") tmp_db.create_conversation("conv1", agent_name="writer")
self._add_msg(tmp_db, "conv1")
convs = tmp_db.list_conversations() convs = tmp_db.list_conversations()
assert convs[0]["agent_name"] == "writer" assert convs[0]["agent_name"] == "writer"
def test_list_filters_by_agent_name(self, tmp_db): def test_list_filters_by_agent_name(self, tmp_db):
tmp_db.create_conversation("c1", agent_name="default") for cid, agent in [("c1", "default"), ("c2", "writer"), ("c3", "default")]:
tmp_db.create_conversation("c2", agent_name="writer") tmp_db.create_conversation(cid, agent_name=agent)
tmp_db.create_conversation("c3", agent_name="default") self._add_msg(tmp_db, cid)
default_convs = tmp_db.list_conversations(agent_name="default") default_convs = tmp_db.list_conversations(agent_name="default")
writer_convs = tmp_db.list_conversations(agent_name="writer") writer_convs = tmp_db.list_conversations(agent_name="writer")
@ -35,14 +41,16 @@ class TestConversationsAgentName:
assert len(all_convs) == 3 assert len(all_convs) == 3
def test_list_without_filter_returns_all(self, tmp_db): def test_list_without_filter_returns_all(self, tmp_db):
tmp_db.create_conversation("c1", agent_name="a") for cid, agent in [("c1", "a"), ("c2", "b")]:
tmp_db.create_conversation("c2", agent_name="b") tmp_db.create_conversation(cid, agent_name=agent)
self._add_msg(tmp_db, cid)
convs = tmp_db.list_conversations() convs = tmp_db.list_conversations()
assert len(convs) == 2 assert len(convs) == 2
def test_list_returns_agent_name_in_results(self, tmp_db): def test_list_returns_agent_name_in_results(self, tmp_db):
tmp_db.create_conversation("c1", agent_name="researcher") tmp_db.create_conversation("c1", agent_name="researcher")
self._add_msg(tmp_db, "c1")
convs = tmp_db.list_conversations() convs = tmp_db.list_conversations()
assert "agent_name" in convs[0] assert "agent_name" in convs[0]
assert convs[0]["agent_name"] == "researcher" assert convs[0]["agent_name"] == "researcher"
@ -52,6 +60,7 @@ class TestConversationsAgentName:
db_path = tmp_path / "test_migrate.db" db_path = tmp_path / "test_migrate.db"
db1 = Database(db_path) db1 = Database(db_path)
db1.create_conversation("c1", agent_name="ops") db1.create_conversation("c1", agent_name="ops")
db1.add_message("c1", "user", "hello")
# Re-init on same DB file triggers migration again # Re-init on same DB file triggers migration again
db2 = Database(db_path) db2 = Database(db_path)
convs = db2.list_conversations() convs = db2.list_conversations()