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) <noreply@anthropic.com>master
parent
e8df7a9750
commit
b857d3cb8c
|
|
@ -17,13 +17,13 @@ logging.basicConfig(
|
||||||
datefmt="%H:%M:%S",
|
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 = Path(__file__).resolve().parent.parent / "logs"
|
||||||
_log_dir.mkdir(exist_ok=True)
|
_log_dir.mkdir(exist_ok=True)
|
||||||
_file_handler = RotatingFileHandler(
|
_file_handler = RotatingFileHandler(
|
||||||
_log_dir / "cheddahbot.log", maxBytes=5 * 1024 * 1024, backupCount=5
|
_log_dir / "cheddahbot.log", maxBytes=5 * 1024 * 1024, backupCount=5
|
||||||
)
|
)
|
||||||
_file_handler.setLevel(logging.WARNING)
|
_file_handler.setLevel(logging.DEBUG)
|
||||||
_file_handler.setFormatter(
|
_file_handler.setFormatter(
|
||||||
logging.Formatter("%(asctime)s [%(name)s] %(levelname)s: %(message)s")
|
logging.Formatter("%(asctime)s [%(name)s] %(levelname)s: %(message)s")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,19 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
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"
|
HEARTBEAT_OK = "HEARTBEAT_OK"
|
||||||
|
|
||||||
# Only tasks in these statuses are eligible for xlsx → ClickUp matching.
|
# 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 ""
|
money_site_url = matched_task.custom_fields.get("IMSURL", "") or ""
|
||||||
if not money_site_url:
|
if not money_site_url:
|
||||||
log.warning("Task %s (%s) missing IMSURL — skipping", task_id, matched_task.name)
|
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)
|
client.update_task_status(task_id, self.config.clickup.error_status)
|
||||||
self._notify(
|
self._notify(
|
||||||
f"Folder watcher: **{filename}** matched task **{matched_task.name}** "
|
f"Folder watcher: **{filename}** matched task **{matched_task.name}** "
|
||||||
|
|
@ -823,6 +841,10 @@ class Scheduler:
|
||||||
|
|
||||||
if "Error" in result and "## Step" not in result:
|
if "Error" in result and "## Step" not in result:
|
||||||
# Pipeline failed — tool handles its own ClickUp error status
|
# 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(
|
self._notify(
|
||||||
f"Folder watcher: pipeline **failed** for **{filename}**.\n"
|
f"Folder watcher: pipeline **failed** for **{filename}**.\n"
|
||||||
f"Error: {result[:200]}",
|
f"Error: {result[:200]}",
|
||||||
|
|
@ -847,6 +869,10 @@ class Scheduler:
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("Folder watcher pipeline error for %s: %s", filename, 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)
|
client.update_task_status(task_id, self.config.clickup.error_status)
|
||||||
finally:
|
finally:
|
||||||
self._unregister_execution(task_id)
|
self._unregister_execution(task_id)
|
||||||
|
|
@ -990,6 +1016,10 @@ class Scheduler:
|
||||||
result = "Error: tool registry not available"
|
result = "Error: tool registry not available"
|
||||||
|
|
||||||
if result.startswith("Error:"):
|
if result.startswith("Error:"):
|
||||||
|
_pipeline_err_log.info(
|
||||||
|
"CONTENT | task=%s | file=%s | result=%s",
|
||||||
|
task_id, filename, result[:500],
|
||||||
|
)
|
||||||
self._notify(
|
self._notify(
|
||||||
f"Content watcher: pipeline **failed** for **{filename}**.\n"
|
f"Content watcher: pipeline **failed** for **{filename}**.\n"
|
||||||
f"Error: {result[:200]}",
|
f"Error: {result[:200]}",
|
||||||
|
|
@ -1014,6 +1044,11 @@ class Scheduler:
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("Content watcher pipeline error for %s: %s", filename, 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:
|
finally:
|
||||||
self._unregister_execution(task_id)
|
self._unregister_execution(task_id)
|
||||||
|
|
||||||
|
|
@ -1125,6 +1160,7 @@ class Scheduler:
|
||||||
has_lb = False
|
has_lb = False
|
||||||
has_content = False
|
has_content = False
|
||||||
matched_names = []
|
matched_names = []
|
||||||
|
matched_error_tasks = []
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if task.status not in _CORA_ELIGIBLE_STATUSES:
|
if task.status not in _CORA_ELIGIBLE_STATUSES:
|
||||||
|
|
@ -1138,6 +1174,8 @@ class Scheduler:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
matched_names.append(task.name)
|
matched_names.append(task.name)
|
||||||
|
if task.status == self.config.clickup.error_status:
|
||||||
|
matched_error_tasks.append(task)
|
||||||
if task.task_type == "Link Building":
|
if task.task_type == "Link Building":
|
||||||
has_lb = True
|
has_lb = True
|
||||||
elif task.task_type in ("Content Creation", "On Page Optimization"):
|
elif task.task_type in ("Content Creation", "On Page Optimization"):
|
||||||
|
|
@ -1174,6 +1212,19 @@ class Scheduler:
|
||||||
)
|
)
|
||||||
return
|
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/
|
# Move original to processed/
|
||||||
processed_dir = xlsx_path.parent / "processed"
|
processed_dir = xlsx_path.parent / "processed"
|
||||||
processed_dir.mkdir(exist_ok=True)
|
processed_dir.mkdir(exist_ok=True)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue