fix(session_store): key InMemorySessionStore structurally instead of by joined string - #1180
Closed
PranavMishra28 wants to merge 1 commit into
Closed
Conversation
…by joined string Closes anthropics#1168. `_key_to_string()` joined the three key components with "/" and every read path parsed them back out by prefix, so any component containing the separator could alias a different logical key. Two concrete consequences, both reproduced against main before changing anything: # {"project_key": "a/b", "session_id": "c"} vs # {"project_key": "a", "session_id": "b", "subpath": "c"} load(a) -> [{'x': 1}, {'x': 2}] # both sessions' entries, merged load(b) -> [{'x': 1}, {'x': 2}] # delete({"project_key": "tenant", "session_id": "x"}) with a # {"session_id": "x/y"} session present victim survives cascade: None # unrelated session destroyed So this is cross-session data bleed on read and data loss on cascade delete, not only the listing ambiguity the issue describes. The store now keys on a `(project_key, session_id, subpath | None)` tuple, and `list_sessions`, `delete`, `list_subkeys` and `size` compare fields rather than slicing strings. No public API or behavior change for keys that contain no separator. Tests: three cases covering the aliasing pairs and the cascade, all failing on the parent commit under both asyncio and trio. They assert on the aliasing pairs rather than the encoding, so they stay valid for any representation that keeps the components distinct.
This was referenced Aug 5, 2026
Author
|
Closing this as a duplicate of #1178, which does the same thing and predates it by two days. I checked whether the issue had been claimed in the comments but did not search for an open PR against it, which is the actual check that would have caught this. Moved the parts that were additive onto #1178: the Redis and S3 example adapters collide identically ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1168.
Problem
_key_to_string()joined the three key components with/, and every read path parsed them back out by prefix, so any component containing the separator could alias a different logical key.The issue describes this as a listing/ambiguity problem. Reproducing it first turned up two sharper consequences:
So it's cross-session bleed on read, and data loss on cascade delete.
Approach
Key on a
(project_key, session_id, subpath | None)tuple.list_sessions,delete,list_subkeysandsizenow compare fields instead of slicing strings. No public API change, and no behavior change for keys containing no separator.Worth noting the shipped Postgres example already does this correctly —
PRIMARY KEY (project_key, session_id, subpath, seq)withWHERE subpath = ''— so structural keying looks like the intended contract rather than something I'm inventing here. This brings the in-memory reference in line with it.Beyond this PR, and deliberately not in it
Two related findings I verified but left alone, since they widen the diff well past the issue:
The conformance harness has no delimiter coverage.
run_session_store_conformanceis what third-party adapters validate against, and it never puts a separator in any component — so an adapter with this exact bug passes it today.Two of the three shipped example adapters have the same bug. Calling their key builders directly:
Redis joins on
":", S3 on"/"; Postgres is immune for the reason above.Adding a delimiter case to the conformance suite would therefore fail the Redis and S3 examples until they're keyed structurally too. I'd rather land the reference fix first and then do conformance plus both adapters as a focused follow-up, but happy to fold it all into this PR if you'd prefer one change — the invariant is the same in all three places.
How tested
tests/test_session_store_conformance.py: the aliasing pair onproject_key, the cascade-delete case onsession_id, and listings/size/list_subkeysreporting components verbatim. All fail on the parent commit under both asyncio and trio (6 failures), pass after.mypyclean.ruffunchanged from base (32 pre-existing findings, identical with this change stashed).Developed with Claude Code; reviewed and tested by Pranav before marking ready for review.