diff --git a/gateway/platforms/slack.py b/gateway/platforms/slack.py index 26184b7eb..afd1a8aa8 100644 --- a/gateway/platforms/slack.py +++ b/gateway/platforms/slack.py @@ -1239,10 +1239,9 @@ class SlackAdapter(BasePlatformAdapter): } choice = choice_map.get(action_id, "deny") - # Prevent double-clicks - if self._approval_resolved.get(msg_ts, False): + # Prevent double-clicks — atomic pop; first caller gets False, others get True (default) + if self._approval_resolved.pop(msg_ts, True): return - self._approval_resolved[msg_ts] = True # Update the message to show the decision and remove buttons label_map = { @@ -1297,8 +1296,7 @@ class SlackAdapter(BasePlatformAdapter): except Exception as exc: logger.error("Failed to resolve gateway approval from Slack button: %s", exc) - # Clean up stale approval state - self._approval_resolved.pop(msg_ts, None) + # (approval state already consumed by atomic pop above) # ----- Thread context fetching ----- diff --git a/hermes_state.py b/hermes_state.py index a845dbb9f..c6825a3e6 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -944,7 +944,8 @@ class SessionDB: try: msg["tool_calls"] = json.loads(msg["tool_calls"]) except (json.JSONDecodeError, TypeError): - pass + logger.warning("Failed to deserialize tool_calls in get_messages, falling back to []") + msg["tool_calls"] = [] result.append(msg) return result @@ -972,7 +973,8 @@ class SessionDB: try: msg["tool_calls"] = json.loads(row["tool_calls"]) except (json.JSONDecodeError, TypeError): - pass + logger.warning("Failed to deserialize tool_calls in conversation replay, falling back to []") + msg["tool_calls"] = [] # Restore reasoning fields on assistant messages so providers # that replay reasoning (OpenRouter, OpenAI, Nous) receive # coherent multi-turn reasoning context. @@ -983,12 +985,14 @@ class SessionDB: try: msg["reasoning_details"] = json.loads(row["reasoning_details"]) except (json.JSONDecodeError, TypeError): - pass + logger.warning("Failed to deserialize reasoning_details, falling back to None") + msg["reasoning_details"] = None if row["codex_reasoning_items"]: try: msg["codex_reasoning_items"] = json.loads(row["codex_reasoning_items"]) except (json.JSONDecodeError, TypeError): - pass + logger.warning("Failed to deserialize codex_reasoning_items, falling back to None") + msg["codex_reasoning_items"] = None messages.append(msg) return messages