diff --git a/hindsight-integrations/claude-code/scripts/recall.py b/hindsight-integrations/claude-code/scripts/recall.py index 6f9d43d641..1f1e5ddbf1 100755 --- a/hindsight-integrations/claude-code/scripts/recall.py +++ b/hindsight-integrations/claude-code/scripts/recall.py @@ -201,9 +201,15 @@ def _dbg(*a): results = response.get("results", []) - # Also recall from any additional banks (e.g. shared user profile bank) + # Also recall from any additional banks (e.g. shared user profile bank). + # Skip the primary (already recalled above) and any repeated entry so + # bidirectional cross-bank setups don't re-recall a bank they already hit. additional_banks = config.get("recallAdditionalBanks", []) + seen_banks = {bank_id} for extra_bank_id in additional_banks: + if extra_bank_id in seen_banks: + continue + seen_banks.add(extra_bank_id) extra_filter = additional_bank_filters.get(extra_bank_id, {}) extra_tags = extra_filter.get("recallTags", recall_tags) or None extra_tag_groups = extra_filter.get("recallTagGroups", tag_groups) or None diff --git a/hindsight-integrations/claude-code/tests/test_hooks.py b/hindsight-integrations/claude-code/tests/test_hooks.py index eafc85a8e8..fce983641b 100644 --- a/hindsight-integrations/claude-code/tests/test_hooks.py +++ b/hindsight-integrations/claude-code/tests/test_hooks.py @@ -275,6 +275,34 @@ def capture_and_respond(req, timeout=None): assert captured[1]["tags"] == ["memory_type:rule"] assert captured[1]["tags_match"] == "all_strict" + def test_additional_banks_skip_primary_and_duplicates(self, monkeypatch, tmp_path): + """The primary bank (and repeated entries) must not be re-recalled.""" + recalled_banks = [] + + def capture_and_respond(req, timeout=None): + if "/recall" in req.full_url: + recalled_banks.append(req.full_url) + return FakeHTTPResponse({"results": []}) + + hook_input = make_hook_input(prompt="anything") + _run_hook( + "recall", + hook_input, + monkeypatch, + tmp_path, + urlopen_side_effect=capture_and_respond, + extra_settings={ + "bankId": "shared-bank", + # primary listed for bidirectional visibility, plus a dup + a real extra + "recallAdditionalBanks": ["shared-bank", "other-bank", "other-bank"], + }, + ) + + # shared-bank recalled once (primary), other-bank once — 2 calls, not 4. + assert sum("shared-bank" in url for url in recalled_banks) == 1 + assert sum("other-bank" in url for url in recalled_banks) == 1 + assert len(recalled_banks) == 2 + def test_disabled_auto_recall_produces_no_output(self, monkeypatch, tmp_path): (tmp_path / "plugin_root").mkdir(exist_ok=True) (tmp_path / "plugin_data").mkdir(exist_ok=True)