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
PeninsulaInd 2026-03-18 18:28:48 -05:00
parent e8df7a9750
commit b857d3cb8c
2 changed files with 53 additions and 2 deletions

View File

@ -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")
)

View File

@ -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)