Add "automation underway" and "error" ClickUp statuses for bot visibility

Tasks now show "automation underway" when the bot picks them up and "error"
on failure, replacing the old "in progress" / "to do" fallbacks that were
invisible on Bryan's ClickUp board. Folder watcher also syncs ClickUp status
on match, missing IMSURL, pipeline failure, success, and exceptions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cora-start
PeninsulaInd 2026-02-20 20:36:58 -06:00
parent 62186d8dec
commit 3f2798d338
6 changed files with 33 additions and 19 deletions

View File

@ -44,6 +44,8 @@ class ClickUpConfig:
poll_statuses: list[str] = field(default_factory=lambda: ["to do"]) poll_statuses: list[str] = field(default_factory=lambda: ["to do"])
review_status: str = "internal review" review_status: str = "internal review"
in_progress_status: str = "in progress" in_progress_status: str = "in progress"
automation_status: str = "automation underway"
error_status: str = "error"
task_type_field_name: str = "Work Category" task_type_field_name: str = "Work Category"
default_auto_execute: bool = False default_auto_execute: bool = False
skill_map: dict = field(default_factory=dict) skill_map: dict = field(default_factory=dict)

View File

@ -339,8 +339,8 @@ class Scheduler:
"custom_fields": task.custom_fields, "custom_fields": task.custom_fields,
} }
# Move to "in progress" on ClickUp immediately # Move to "automation underway" on ClickUp immediately
client.update_task_status(task_id, self.config.clickup.in_progress_status) client.update_task_status(task_id, self.config.clickup.automation_status)
self.db.kv_set(kv_key, json.dumps(state)) self.db.kv_set(kv_key, json.dumps(state))
log.info("Executing ClickUp task: %s%s", task.name, tool_name) log.info("Executing ClickUp task: %s%s", task.name, tool_name)
@ -371,8 +371,8 @@ class Scheduler:
task_id, task_id,
f"⚠️ CheddahBot could not execute this task.\n\n{result[:2000]}", f"⚠️ CheddahBot could not execute this task.\n\n{result[:2000]}",
) )
# Move back to "to do" so it can be retried # Move to "error" so Bryan can see what happened
client.update_task_status(task_id, "to do") client.update_task_status(task_id, self.config.clickup.error_status)
self._notify( self._notify(
f"ClickUp task skipped: **{task.name}**\n" f"ClickUp task skipped: **{task.name}**\n"
@ -428,8 +428,8 @@ class Scheduler:
client.add_comment( client.add_comment(
task_id, f"❌ CheddahBot failed to complete this task.\n\nError: {str(e)[:2000]}" task_id, f"❌ CheddahBot failed to complete this task.\n\nError: {str(e)[:2000]}"
) )
# Move back to "to do" so it can be retried after reset # Move to "error" so Bryan can see what happened
client.update_task_status(task_id, "to do") client.update_task_status(task_id, self.config.clickup.error_status)
self._notify( self._notify(
f"ClickUp task failed: **{task.name}**\n" f"ClickUp task failed: **{task.name}**\n"
@ -547,6 +547,11 @@ class Scheduler:
# Extract tool args from matched task # Extract tool args from matched task
task_id = matched_task.id task_id = matched_task.id
log.info("Matched '%s' to ClickUp task %s (%s)", filename, task_id, matched_task.name) log.info("Matched '%s' to ClickUp task %s (%s)", filename, task_id, matched_task.name)
# Set ClickUp status to "automation underway"
client = self._get_clickup_client()
client.update_task_status(task_id, self.config.clickup.automation_status)
self._notify( self._notify(
f"Folder watcher: matched **{filename}** to ClickUp task **{matched_task.name}**.\n" f"Folder watcher: matched **{filename}** to ClickUp task **{matched_task.name}**.\n"
f"Starting Cora Backlinks pipeline...", f"Starting Cora Backlinks pipeline...",
@ -570,6 +575,8 @@ class Scheduler:
} }
), ),
) )
# Set ClickUp status to "error" so it's visible on the board
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}** "
f"but **IMSURL is empty**. Set the IMSURL field in ClickUp before " f"but **IMSURL is empty**. Set the IMSURL field in ClickUp before "
@ -614,6 +621,7 @@ class Scheduler:
} }
), ),
) )
client.update_task_status(task_id, self.config.clickup.error_status)
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]}",
@ -641,6 +649,7 @@ class Scheduler:
} }
), ),
) )
client.update_task_status(task_id, self.config.clickup.review_status)
self._notify( self._notify(
f"Folder watcher: pipeline **completed** for **{filename}**.\n" f"Folder watcher: pipeline **completed** for **{filename}**.\n"
f"ClickUp task: {matched_task.name}", f"ClickUp task: {matched_task.name}",
@ -661,6 +670,7 @@ class Scheduler:
} }
), ),
) )
client.update_task_status(task_id, self.config.clickup.error_status)
def _match_xlsx_to_clickup(self, normalized_stem: str): def _match_xlsx_to_clickup(self, normalized_stem: str):
"""Find a ClickUp Link Building task whose Keyword matches the file stem. """Find a ClickUp Link Building task whose Keyword matches the file stem.

View File

@ -275,11 +275,11 @@ def _find_clickup_task(ctx: dict, keyword: str) -> str:
if db: if db:
db.kv_set(f"clickup:task:{task_id}:state", json.dumps(state)) db.kv_set(f"clickup:task:{task_id}:state", json.dumps(state))
# Move to "in progress" # Move to "automation underway"
cu_client2 = _get_clickup_client(ctx) cu_client2 = _get_clickup_client(ctx)
if cu_client2: if cu_client2:
try: try:
cu_client2.update_task_status(task_id, config.clickup.in_progress_status) cu_client2.update_task_status(task_id, config.clickup.automation_status)
except Exception as e: except Exception as e:
log.warning("Failed to update ClickUp status for %s: %s", task_id, e) log.warning("Failed to update ClickUp status for %s: %s", task_id, e)
finally: finally:
@ -361,9 +361,7 @@ def _fail_clickup_task(ctx: dict | None, task_id: str, error_msg: str) -> None:
return return
config = ctx.get("config") config = ctx.get("config")
skill_map = config.clickup.skill_map if config else {} error_status = config.clickup.error_status if config else "error"
lb_map = skill_map.get("Link Building", {})
error_status = lb_map.get("error_status", "internal review")
db = ctx.get("db") db = ctx.get("db")
if db: if db:

View File

@ -116,11 +116,11 @@ def _find_clickup_task(ctx: dict, company_name: str) -> str:
if db: if db:
db.kv_set(f"clickup:task:{task_id}:state", json.dumps(state)) db.kv_set(f"clickup:task:{task_id}:state", json.dumps(state))
# Move to "in progress" on ClickUp # Move to "automation underway" on ClickUp
cu_client2 = _get_clickup_client(ctx) cu_client2 = _get_clickup_client(ctx)
if cu_client2: if cu_client2:
try: try:
cu_client2.update_task_status(task_id, config.clickup.in_progress_status) cu_client2.update_task_status(task_id, config.clickup.automation_status)
except Exception as e: except Exception as e:
log.warning("Failed to update ClickUp status for %s: %s", task_id, e) log.warning("Failed to update ClickUp status for %s: %s", task_id, e)
finally: finally:
@ -516,13 +516,13 @@ def write_press_releases(
if cu_client: if cu_client:
try: try:
config = ctx["config"] config = ctx["config"]
cu_client.update_task_status(clickup_task_id, config.clickup.in_progress_status) cu_client.update_task_status(clickup_task_id, config.clickup.automation_status)
cu_client.add_comment( cu_client.add_comment(
clickup_task_id, clickup_task_id,
f"🔄 CheddahBot starting press release creation.\n\n" f"🔄 CheddahBot starting press release creation.\n\n"
f"Topic: {topic}\nCompany: {company_name}", f"Topic: {topic}\nCompany: {company_name}",
) )
log.info("ClickUp task %s set to in-progress", clickup_task_id) log.info("ClickUp task %s set to automation-underway", clickup_task_id)
except Exception as e: except Exception as e:
log.warning("ClickUp start-sync failed for %s: %s", clickup_task_id, e) log.warning("ClickUp start-sync failed for %s: %s", clickup_task_id, e)

View File

@ -45,6 +45,8 @@ clickup:
poll_statuses: ["to do"] poll_statuses: ["to do"]
review_status: "internal review" review_status: "internal review"
in_progress_status: "in progress" in_progress_status: "in progress"
automation_status: "automation underway"
error_status: "error"
task_type_field_name: "Work Category" task_type_field_name: "Work Category"
default_auto_execute: false default_auto_execute: false
skill_map: skill_map:
@ -60,7 +62,7 @@ clickup:
tool: "run_link_building" tool: "run_link_building"
auto_execute: false auto_execute: false
complete_status: "complete" complete_status: "complete"
error_status: "internal review" error_status: "error"
field_mapping: field_mapping:
lb_method: "LB Method" lb_method: "LB Method"
project_name: "task_name" project_name: "task_name"

View File

@ -32,6 +32,8 @@ class _FakeClickUpConfig:
poll_statuses: list[str] = field(default_factory=lambda: ["to do"]) poll_statuses: list[str] = field(default_factory=lambda: ["to do"])
review_status: str = "internal review" review_status: str = "internal review"
in_progress_status: str = "in progress" in_progress_status: str = "in progress"
automation_status: str = "automation underway"
error_status: str = "error"
task_type_field_name: str = "Work Category" task_type_field_name: str = "Work Category"
default_auto_execute: bool = True default_auto_execute: bool = True
skill_map: dict = field(default_factory=lambda: {"Press Release": _PR_MAPPING}) skill_map: dict = field(default_factory=lambda: {"Press Release": _PR_MAPPING})
@ -220,7 +222,7 @@ class TestExecuteTask:
mock_client.update_task_status.assert_any_call( mock_client.update_task_status.assert_any_call(
"t1", "t1",
"in progress", "automation underway",
) )
raw = tmp_db.kv_get("clickup:task:t1:state") raw = tmp_db.kv_get("clickup:task:t1:state")
@ -266,7 +268,7 @@ class TestExecuteTask:
assert "output/pr.docx" in state["deliverable_paths"] assert "output/pr.docx" in state["deliverable_paths"]
def test_failure_flow(self, tmp_db): def test_failure_flow(self, tmp_db):
"""Failed: state=failed, error comment, back to 'to do'.""" """Failed: state=failed, error comment, status set to 'error'."""
config = _FakeConfig() config = _FakeConfig()
agent = MagicMock() agent = MagicMock()
agent._tools = MagicMock() agent._tools = MagicMock()
@ -288,7 +290,7 @@ class TestExecuteTask:
) )
scheduler._execute_task(task) scheduler._execute_task(task)
mock_client.update_task_status.assert_any_call("t1", "to do") mock_client.update_task_status.assert_any_call("t1", "error")
mock_client.add_comment.assert_called_once() mock_client.add_comment.assert_called_once()
comment_text = mock_client.add_comment.call_args[0][1] comment_text = mock_client.add_comment.call_args[0][1]
assert "failed" in comment_text.lower() assert "failed" in comment_text.lower()