From b857d3cb8ced8c508b363885f346f9d392ef71f3 Mon Sep 17 00:00:00 2001 From: PeninsulaInd Date: Wed, 18 Mar 2026 18:28:48 -0500 Subject: [PATCH] Fix missing ClickUp comments on error status transitions and add pipeline error audit log - File handler now captures DEBUG+ (was WARNING) for full pipeline visibility - Add ClickUp comments at every error status transition (missing IMSURL, pipeline crash, content crash) - Content watcher exception now also sets error status (was silently failing) - Cora distributor resets matched error tasks to "running cora" when new XLSX arrives - Add dedicated logs/pipeline_errors.log for tool-handled errors (audit trail) Co-Authored-By: Claude Opus 4.6 (1M context) --- cheddahbot/__main__.py | 4 ++-- cheddahbot/scheduler.py | 51 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/cheddahbot/__main__.py b/cheddahbot/__main__.py index 61ff6ba..8ff104c 100644 --- a/cheddahbot/__main__.py +++ b/cheddahbot/__main__.py @@ -17,13 +17,13 @@ logging.basicConfig( datefmt="%H:%M:%S", ) -# Warnings and errors to rotating log file +# All levels to rotating log file (DEBUG+) _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.setLevel(logging.DEBUG) _file_handler.setFormatter( logging.Formatter("%(asctime)s [%(name)s] %(levelname)s: %(message)s") ) diff --git a/cheddahbot/scheduler.py b/cheddahbot/scheduler.py index 8e0418a..92a80c6 100644 --- a/cheddahbot/scheduler.py +++ b/cheddahbot/scheduler.py @@ -23,6 +23,19 @@ if TYPE_CHECKING: log = logging.getLogger(__name__) +# Dedicated logger for "tool returned error but likely handled it" cases. +# Writes to logs/pipeline_errors.log for manual review. +_pipeline_err_log = logging.getLogger("cheddahbot.pipeline_errors") +_pipeline_err_log.propagate = False +_pe_dir = Path(__file__).resolve().parent.parent / "logs" +_pe_dir.mkdir(exist_ok=True) +_pe_handler = logging.FileHandler(_pe_dir / "pipeline_errors.log", encoding="utf-8") +_pe_handler.setFormatter( + logging.Formatter("%(asctime)s | %(message)s") +) +_pipeline_err_log.addHandler(_pe_handler) +_pipeline_err_log.setLevel(logging.INFO) + HEARTBEAT_OK = "HEARTBEAT_OK" # Only tasks in these statuses are eligible for xlsx → ClickUp matching. @@ -789,6 +802,11 @@ class Scheduler: money_site_url = matched_task.custom_fields.get("IMSURL", "") or "" if not money_site_url: log.warning("Task %s (%s) missing IMSURL — skipping", task_id, matched_task.name) + client.add_comment( + task_id, + "❌ Link building skipped — IMSURL field is empty. " + "Set the IMSURL field in ClickUp so the pipeline knows where to build links.", + ) client.update_task_status(task_id, self.config.clickup.error_status) self._notify( f"Folder watcher: **{filename}** matched task **{matched_task.name}** " @@ -823,6 +841,10 @@ class Scheduler: if "Error" in result and "## Step" not in result: # Pipeline failed — tool handles its own ClickUp error status + _pipeline_err_log.info( + "LINKBUILDING | task=%s | file=%s | result=%s", + task_id, filename, result[:500], + ) self._notify( f"Folder watcher: pipeline **failed** for **{filename}**.\n" f"Error: {result[:200]}", @@ -847,6 +869,10 @@ class Scheduler: except Exception as e: log.error("Folder watcher pipeline error for %s: %s", filename, e) + client.add_comment( + task_id, + f"❌ Link building pipeline crashed.\n\nError: {str(e)[:2000]}", + ) client.update_task_status(task_id, self.config.clickup.error_status) finally: self._unregister_execution(task_id) @@ -990,6 +1016,10 @@ class Scheduler: result = "Error: tool registry not available" if result.startswith("Error:"): + _pipeline_err_log.info( + "CONTENT | task=%s | file=%s | result=%s", + task_id, filename, result[:500], + ) self._notify( f"Content watcher: pipeline **failed** for **{filename}**.\n" f"Error: {result[:200]}", @@ -1014,6 +1044,11 @@ class Scheduler: except Exception as e: log.error("Content watcher pipeline error for %s: %s", filename, e) + client.add_comment( + task_id, + f"❌ Content pipeline crashed.\n\nError: {str(e)[:2000]}", + ) + client.update_task_status(task_id, self.config.clickup.error_status) finally: self._unregister_execution(task_id) @@ -1125,6 +1160,7 @@ class Scheduler: has_lb = False has_content = False matched_names = [] + matched_error_tasks = [] for task in tasks: if task.status not in _CORA_ELIGIBLE_STATUSES: @@ -1138,6 +1174,8 @@ class Scheduler: continue matched_names.append(task.name) + if task.status == self.config.clickup.error_status: + matched_error_tasks.append(task) if task.task_type == "Link Building": has_lb = True elif task.task_type in ("Content Creation", "On Page Optimization"): @@ -1174,6 +1212,19 @@ class Scheduler: ) return + # Reset any matched tasks that were in "error" back to "running cora" + # so the pipeline picks them up again. + for task in matched_error_tasks: + try: + client.update_task_status(task.id, "running cora") + client.add_comment( + task.id, + f"New Cora XLSX distributed — resetting from error to running cora.", + ) + log.info("Distributor: reset task %s (%s) from error → running cora", task.id, task.name) + except Exception as e: + log.warning("Distributor: failed to reset task %s: %s", task.id, e) + # Move original to processed/ processed_dir = xlsx_path.parent / "processed" processed_dir.mkdir(exist_ok=True)