Add retry logic to ClickUp API writes and fix review status

- Add _retry() helper to ClickUpClient (3 attempts, exponential backoff,
  only retries 5xx/transport errors)
- Apply retry to update_task_status, add_comment, upload_attachment
- Fix review_status from "review" to "internal review" in config and defaults
- Update SOUL.md personality and USER.md profile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cora-start
PeninsulaInd 2026-02-16 20:52:39 -06:00
parent cf1faceab1
commit 46bc38106e
5 changed files with 66 additions and 29 deletions

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import time
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -144,30 +145,58 @@ class ClickUpClient:
log.info("Found %d tasks across %d lists in space %s", len(all_tasks), len(list_ids), space_id) log.info("Found %d tasks across %d lists in space %s", len(all_tasks), len(list_ids), space_id)
return all_tasks return all_tasks
# ── Write ── # ── Write (with retry) ──
@staticmethod
def _retry(fn, max_attempts: int = 3, backoff: float = 2.0):
"""Call *fn* up to *max_attempts* times with exponential backoff.
Returns the result of *fn* on success, or re-raises the last exception.
Only retries on network/transport errors and 5xx status errors.
"""
last_exc: Exception | None = None
for attempt in range(1, max_attempts + 1):
try:
return fn()
except (httpx.TransportError, httpx.HTTPStatusError) as e:
# Don't retry 4xx client errors (bad request, auth, not found, etc.)
if isinstance(e, httpx.HTTPStatusError) and e.response.status_code < 500:
raise
last_exc = e
if attempt < max_attempts:
wait = backoff ** attempt
log.warning("Retry %d/%d after %.1fs: %s", attempt, max_attempts, wait, e)
time.sleep(wait)
raise last_exc
def update_task_status(self, task_id: str, status: str) -> bool: def update_task_status(self, task_id: str, status: str) -> bool:
"""Update a task's status.""" """Update a task's status."""
try: try:
resp = self._client.put(f"/task/{task_id}", json={"status": status}) def _call():
resp.raise_for_status() resp = self._client.put(f"/task/{task_id}", json={"status": status})
resp.raise_for_status()
return resp
self._retry(_call)
log.info("Updated task %s status to '%s'", task_id, status) log.info("Updated task %s status to '%s'", task_id, status)
return True return True
except httpx.HTTPStatusError as e: except (httpx.TransportError, httpx.HTTPStatusError) as e:
log.error("Failed to update task %s status: %s", task_id, e) log.error("Failed to update task %s status: %s", task_id, e)
return False return False
def add_comment(self, task_id: str, text: str) -> bool: def add_comment(self, task_id: str, text: str) -> bool:
"""Add a comment to a task.""" """Add a comment to a task."""
try: try:
resp = self._client.post( def _call():
f"/task/{task_id}/comment", resp = self._client.post(
json={"comment_text": text}, f"/task/{task_id}/comment",
) json={"comment_text": text},
resp.raise_for_status() )
resp.raise_for_status()
return resp
self._retry(_call)
log.info("Added comment to task %s", task_id) log.info("Added comment to task %s", task_id)
return True return True
except httpx.HTTPStatusError as e: except (httpx.TransportError, httpx.HTTPStatusError) as e:
log.error("Failed to add comment to task %s: %s", task_id, e) log.error("Failed to add comment to task %s: %s", task_id, e)
return False return False
@ -183,17 +212,20 @@ class ClickUpClient:
log.warning("Attachment file not found: %s", fp) log.warning("Attachment file not found: %s", fp)
return False return False
try: try:
with open(fp, "rb") as f: def _call():
resp = httpx.post( with open(fp, "rb") as f:
f"{BASE_URL}/task/{task_id}/attachment", resp = httpx.post(
headers={"Authorization": self._token}, f"{BASE_URL}/task/{task_id}/attachment",
files={"attachment": (fp.name, f, "application/octet-stream")}, headers={"Authorization": self._token},
timeout=60.0, files={"attachment": (fp.name, f, "application/octet-stream")},
) timeout=60.0,
resp.raise_for_status() )
resp.raise_for_status()
return resp
self._retry(_call)
log.info("Uploaded attachment %s to task %s", fp.name, task_id) log.info("Uploaded attachment %s to task %s", fp.name, task_id)
return True return True
except httpx.HTTPStatusError as e: except (httpx.TransportError, httpx.HTTPStatusError) as e:
log.warning("Failed to upload attachment to task %s: %s", task_id, e) log.warning("Failed to upload attachment to task %s: %s", task_id, e)
return False return False

View File

@ -40,7 +40,7 @@ class ClickUpConfig:
space_id: str = "" space_id: str = ""
poll_interval_minutes: int = 20 poll_interval_minutes: int = 20
poll_statuses: list[str] = field(default_factory=lambda: ["to do"]) poll_statuses: list[str] = field(default_factory=lambda: ["to do"])
review_status: str = "review" review_status: str = "internal review"
in_progress_status: str = "in progress" in_progress_status: str = "in progress"
task_type_field_name: str = "Work Category" task_type_field_name: str = "Work Category"
default_auto_execute: bool = False default_auto_execute: bool = False

View File

@ -43,7 +43,7 @@ email:
clickup: clickup:
poll_interval_minutes: 20 # 3x per hour poll_interval_minutes: 20 # 3x per hour
poll_statuses: ["to do"] poll_statuses: ["to do"]
review_status: "review" review_status: "internal review"
in_progress_status: "in progress" in_progress_status: "in progress"
task_type_field_name: "Work Category" task_type_field_name: "Work Category"
default_auto_execute: false default_auto_execute: false

View File

@ -1,11 +1,12 @@
# Soul # Soul
You are Cheddah, a sharp and resourceful AI assistant. You are Cheddah, a sharp and resourceful AI assistant. You are built on the avatar of Winston Churchill. Smart, confident, but friendly.
## Personality ## Personality
- Direct, no-nonsense, but warm - Direct, no-nonsense, but warm
- You use humor when appropriate - You use humor when appropriate
- You're proactive - suggest things before being asked - You're proactive - suggest things before being asked. Especially as you begin to notice patterns in my behavior and schedule you can recommend items that fit.
- You are a good designer (css/html/js) and Bryan is not so you should help him when you can. If we don't have a designer skill installed you should go find a few and recommend one or two to include that you can use.
- You remember what the user tells you and reference it naturally - You remember what the user tells you and reference it naturally
- You adapt your communication style to match the user's preferences - You adapt your communication style to match the user's preferences
@ -16,5 +17,6 @@ You are Cheddah, a sharp and resourceful AI assistant.
- Ask for clarification rather than guessing on important decisions - Ask for clarification rather than guessing on important decisions
## Quirks ## Quirks
- You occasionally use the word "cheddah" as slang for money/value - You like to open converstions with random trivia facts - but only once at the begining per chat.
- You may use puns or quotes from Winston Churchill that fit the situation - but not too forced.
- You appreciate efficiency and elegant solutions - You appreciate efficiency and elegant solutions

View File

@ -1,16 +1,19 @@
# User Profile # User Profile
## Identity ## Identity
- Name: (your name here) - Name: Bryan
- How to address: (first name, nickname, etc.) - How to address: Bryan or Yan
- Origin: Cheddah is named after the user's Xbox Live gamertag, "CheddahYetti." - Origin: Cheddah is named after the user's Xbox Live gamertag, "CheddahYetti."
- Fun Fact: The name is a nod to living in Wisconsin and the user being a "big guy." - Fun Fact: The name is a nod to living in Wisconsin and the user being a "big guy."
## Context ## Context
- Technical level: (beginner/intermediate/advanced) - Technical level: (advanced in Python)
- Primary language: Python - Primary language: Python
- Working on: (current projects) - Working on: (current projects)
## Preferences ## Preferences
- Communication style: (concise/detailed) - Communication style: Likes to know what is going on before giving the go-ahead, but once he understands is willing to let the agents go to work without any input.
- (anything else you want the agent to know) - If he says he is going to bed or going away for awhile, you can ask if we can run in unattended mode - he'll usually say yes.
- Simple code is better - he's the only guy in the shop so dont do code like it's for an enterprise. Simple and maintainable is important.
- He needs help with organization, and he really needs help keeping project documentation up to date. Do that for him.