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
import logging
import time
from dataclasses import dataclass, field
from pathlib import Path
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)
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:
"""Update a task's status."""
try:
def _call():
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)
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)
return False
def add_comment(self, task_id: str, text: str) -> bool:
"""Add a comment to a task."""
try:
def _call():
resp = self._client.post(
f"/task/{task_id}/comment",
json={"comment_text": text},
)
resp.raise_for_status()
return resp
self._retry(_call)
log.info("Added comment to task %s", task_id)
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)
return False
@ -183,6 +212,7 @@ class ClickUpClient:
log.warning("Attachment file not found: %s", fp)
return False
try:
def _call():
with open(fp, "rb") as f:
resp = httpx.post(
f"{BASE_URL}/task/{task_id}/attachment",
@ -191,9 +221,11 @@ class ClickUpClient:
timeout=60.0,
)
resp.raise_for_status()
return resp
self._retry(_call)
log.info("Uploaded attachment %s to task %s", fp.name, task_id)
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)
return False

View File

@ -40,7 +40,7 @@ class ClickUpConfig:
space_id: str = ""
poll_interval_minutes: int = 20
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"
task_type_field_name: str = "Work Category"
default_auto_execute: bool = False

View File

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

View File

@ -1,11 +1,12 @@
# 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
- Direct, no-nonsense, but warm
- 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 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
## 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

View File

@ -1,16 +1,19 @@
# User Profile
## Identity
- Name: (your name here)
- How to address: (first name, nickname, etc.)
- Name: Bryan
- How to address: Bryan or Yan
- 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."
## Context
- Technical level: (beginner/intermediate/advanced)
- Technical level: (advanced in Python)
- Primary language: Python
- Working on: (current projects)
## Preferences
- Communication style: (concise/detailed)
- (anything else you want the agent to know)
- 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.
- 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.