Skip to content

Commit e211beb

Browse files
vishal-balaclaude
andauthored
fix(router): remove persisted route_config key on SemanticRouter.delete() (#635)
## Motivation A `SemanticRouter` persists its configuration as a standalone JSON key, `{name}:route_config`, written when the router is constructed and rewritten on every route change. `delete()`, however, only drops the router's search index — it never removes that key. The index is built over hash documents, and `FT.DROPINDEX ... DD` deletes only the documents the index actually tracks, so the JSON config key falls outside its reach and survives every call to `delete()`. The immediate consequence is orphaned `:route_config` keys accumulating in Redis. The subtler one is a correctness gap: `from_existing()` loads a router straight from that key, so a router that was supposedly deleted can be silently resurrected from stale config. It also leaves residue behind in the test suite, where it contributed to the broader Redis-backed isolation problems tracked in #546. ## Fix `delete()` now removes `{name}:route_config` explicitly after dropping the index, so deleting a router fully clears its state. The change is confined to `SemanticRouter.delete()`; there is no async router variant to mirror. A regression test constructs a router, confirms the config key exists, calls `delete()`, and then asserts both that the key is gone and that `from_existing()` on the same name raises rather than reloading stale config — locking in the behaviour described above. Closes #634. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small lifecycle fix in router teardown with a focused regression test; no changes to routing, auth, or data semantics beyond complete cleanup on delete. > > **Overview** > **`SemanticRouter.delete()`** now drops the search index and explicitly deletes the standalone Redis JSON key `{name}:route_config`, which is not removed by `FT.DROPINDEX` because it sits outside the index. > > This prevents orphaned config keys and stops **`from_existing()`** from reloading a router that was supposed to be deleted. > > An integration test asserts the config key is gone after **`delete()`** and that **`from_existing()`** raises when no valid config remains. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 23805cff1c06371b2377abeee442b09dd1b09a74. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a1f2891 commit e211beb

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

redisvl/extensions/router/semantic.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,11 @@ def remove_route(self, route_name: str) -> None:
577577
self._update_router_state()
578578

579579
def delete(self) -> None:
580-
"""Delete the semantic router index."""
580+
"""Delete the semantic router index and its persisted route config."""
581581
self._index.delete(drop=True)
582+
# The route config is stored as a standalone JSON key that is not
583+
# tracked by the search index, so it must be removed explicitly.
584+
self._index.client.delete(f"{self.name}:route_config") # type: ignore
582585

583586
def clear(self) -> None:
584587
"""Flush all routes from the semantic router index."""

tests/integration/test_semantic_router.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,34 @@ def test_from_existing(client, redis_url, routes, redis_test_name):
685685
router.delete()
686686

687687

688+
def test_delete_removes_route_config_key(
689+
client, routes, hf_vectorizer, redis_test_name
690+
):
691+
"""delete() must remove the persisted {name}:route_config key (issue #634)."""
692+
skip_if_no_redis_search(client)
693+
694+
router = SemanticRouter(
695+
name=redis_test_name("test_router_delete"),
696+
routes=routes,
697+
routing_config=RoutingConfig(max_k=2),
698+
redis_client=client,
699+
overwrite=True,
700+
vectorizer=hf_vectorizer,
701+
)
702+
config_key = f"{router.name}:route_config"
703+
704+
# The config key is written when the router is constructed.
705+
assert client.exists(config_key)
706+
707+
router.delete()
708+
709+
# delete() must drop the index AND remove the standalone config key, so a
710+
# subsequent from_existing() finds no stale config to load.
711+
assert not client.exists(config_key)
712+
with pytest.raises(ValueError):
713+
SemanticRouter.from_existing(name=router.name, redis_client=client)
714+
715+
688716
def test_get_route_references(semantic_router):
689717
# Get references for a specific route
690718
refs = semantic_router.get_route_references(route_name="greeting")

0 commit comments

Comments
 (0)