From f891ead635e4995cec6049716316e51621c076ba Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:47:23 -0400 Subject: [PATCH] Fix fetch-classify INIT crash and thread-state writes 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. --- src/requirements.txt | 3 +++ src/shared/dynamo.py | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/requirements.txt b/src/requirements.txt index 8af45c3..cc6ba64 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -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 diff --git a/src/shared/dynamo.py b/src/shared/dynamo.py index e5960e5..bbeba3f 100644 --- a/src/shared/dynamo.py +++ b/src/shared/dynamo.py @@ -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, )