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
parent
cf1faceab1
commit
46bc38106e
|
|
@ -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:
|
||||
resp = self._client.put(f"/task/{task_id}", json={"status": status})
|
||||
resp.raise_for_status()
|
||||
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:
|
||||
resp = self._client.post(
|
||||
f"/task/{task_id}/comment",
|
||||
json={"comment_text": text},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
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,17 +212,20 @@ class ClickUpClient:
|
|||
log.warning("Attachment file not found: %s", fp)
|
||||
return False
|
||||
try:
|
||||
with open(fp, "rb") as f:
|
||||
resp = httpx.post(
|
||||
f"{BASE_URL}/task/{task_id}/attachment",
|
||||
headers={"Authorization": self._token},
|
||||
files={"attachment": (fp.name, f, "application/octet-stream")},
|
||||
timeout=60.0,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
def _call():
|
||||
with open(fp, "rb") as f:
|
||||
resp = httpx.post(
|
||||
f"{BASE_URL}/task/{task_id}/attachment",
|
||||
headers={"Authorization": self._token},
|
||||
files={"attachment": (fp.name, f, "application/octet-stream")},
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue