diff --git a/gateway/platforms/feishu.py b/gateway/platforms/feishu.py index 40711d9680..e1528b9bca 100644 --- a/gateway/platforms/feishu.py +++ b/gateway/platforms/feishu.py @@ -3953,7 +3953,14 @@ class FeishuAdapter(BasePlatformAdapter): if isinstance(seen_data, list): entries: Dict[str, float] = {str(item).strip(): 0.0 for item in seen_data if str(item).strip()} elif isinstance(seen_data, dict): - entries = {k: float(v) for k, v in seen_data.items() if isinstance(k, str) and k.strip()} + entries = {} + for key, value in seen_data.items(): + if not isinstance(key, str) or not key.strip(): + continue + try: + entries[key] = float(value) + except (TypeError, ValueError): + continue else: return # Filter out TTL-expired entries (entries saved with ts=0.0 are treated as immortal diff --git a/tests/gateway/test_feishu.py b/tests/gateway/test_feishu.py index 0444261b18..f4ac80f2e1 100644 --- a/tests/gateway/test_feishu.py +++ b/tests/gateway/test_feishu.py @@ -3197,6 +3197,37 @@ class TestDedupTTL(unittest.TestCase): with patch.object(adapter, "_persist_seen_message_ids"): self.assertFalse(adapter._is_duplicate("om_old")) + @patch.dict(os.environ, {}, clear=True) + def test_load_tolerates_malformed_timestamp_values(self): + """Regression #13632 — a non-numeric timestamp in the persisted + dedup state must not crash adapter startup. The bad key is + skipped; the rest of the state loads. + """ + import tempfile + from gateway.config import PlatformConfig + from gateway.platforms.feishu import FeishuAdapter + + with tempfile.TemporaryDirectory() as temp_home: + with patch.dict(os.environ, {"HERMES_HOME": temp_home}, clear=True): + adapter = FeishuAdapter(PlatformConfig()) + adapter._dedup_state_path.parent.mkdir(parents=True, exist_ok=True) + adapter._dedup_state_path.write_text( + json.dumps( + { + "message_ids": { + "om_good": time.time(), + "om_bad_str": "not-a-timestamp", + "om_bad_null": None, + } + } + ), + encoding="utf-8", + ) + adapter._load_seen_message_ids() + assert "om_good" in adapter._seen_message_ids + assert "om_bad_str" not in adapter._seen_message_ids + assert "om_bad_null" not in adapter._seen_message_ids + @patch.dict(os.environ, {}, clear=True) def test_persist_saves_timestamps_as_dict(self): from gateway.config import PlatformConfig