Fix fetch-classify INIT crash and thread-state writes (#41)

fetch-classify has been down since 2026-05-05 (100% error rate, every
5-min poll failing silently at INIT). Three stacked failures:

- typing_extensions was not resolved into the bundle by PythonFunction
  bundling, so every import died with Runtime.ImportModuleError. Pin it
  explicitly in src/requirements.txt.
- DynamoDB UpdateExpressions used the reserved keyword ttl unaliased,
  failing every save_thread_state/update_task_status call once imports
  were fixed. Alias as #ttl via ExpressionAttributeNames.
- (Out-of-band) classification model claude-haiku-4-5 was never enabled
  in Bedrock model access; agreement accepted account-side 2026-06-03.

Verified live: manual invoke returns processed=1 errors=0.
This commit is contained in:
Adam Moussa 2026-06-03 14:55:56 -04:00 • committed by GitHub
parent 52ad35d6d1
commit 97456f6104
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 9 additions and 4 deletions

View file

@ -2,3 +2,6 @@ google-api-python-client>=2.197.0
google-auth>=2.53.0
python-ulid>=3.1.0
requests>=2.34.2
# Transitive dep not reliably resolved during bundling; absence breaks all
# imports at INIT (Runtime.ImportModuleError, fetch-classify down 05-05→06-03)
typing_extensions>=4.12

View file

@ -71,9 +71,10 @@ def save_thread_state(thread_id, message, adam_email):
"last_message_date = :now, "
"subject = :subject, "
"adam_last_reply_date = :now, "
"ttl = :ttl "
"#ttl = :ttl "
"REMOVE unanswered_since"
),
ExpressionAttributeNames={"#ttl": "ttl"},
ExpressionAttributeValues={
":from_email": message["from_email"],
":now": now_iso,
@ -89,8 +90,9 @@ def save_thread_state(thread_id, message, adam_email):
"last_message_date = :now, "
"subject = :subject, "
"unanswered_since = if_not_exists(unanswered_since, :now), "
"ttl = :ttl"
"#ttl = :ttl"
),
ExpressionAttributeNames={"#ttl": "ttl"},
ExpressionAttributeValues={
":from_email": message["from_email"],
":now": now_iso,
@ -252,7 +254,7 @@ def get_tasks(status_filter=None):
def update_task_status(task_id, status):
update_expr = "SET #s = :status, ttl = :ttl"
update_expr = "SET #s = :status, #ttl = :ttl"
expr_values = {":status": status, ":ttl": _ttl_90_days()}
if status == "completed":
update_expr += ", completed_at = :now"
@ -261,7 +263,7 @@ def update_task_status(task_id, status):
_get_table().update_item(
Key={"pk": f"TASK#{task_id}", "sk": "TASK"},
UpdateExpression=update_expr,
ExpressionAttributeNames={"#s": "status"},
ExpressionAttributeNames={"#s": "status", "#ttl": "ttl"},
ExpressionAttributeValues=expr_values,
)