fix(py): greeting reflects the session that registered its table - #304
Merged
cpsievert merged 3 commits intoSep 12, 2026
Merged
Conversation
The welcome greeting is generated asynchronously, after .server() already returns, by asking the LLM to describe the registered table(s). It reads the shared, mutable QueryChatGreeter.tables and QueryChat instance's data sources at that later point -- so a later session's own server(data_source=...) call can mutate both before an earlier session's greeting actually runs, and that earlier session's greeting ends up describing the wrong table. .server() now captures greeting_tables=list(self.greeter.tables) synchronously at call time and threads it (alongside the already per-session data_sources snapshot) through mod_server() to a new private QueryChatGreeter._generate_async_snapshot(), so the async greeting no longer reads live, shared state at generation time. Kept off the public generate()/generate_async()/build_client() API -- this is an internal fix for a race the per-session pattern introduces, not a new capability.
cpsievert
force-pushed
the
fix/py-server-greeting-snapshot
branch
from
September 12, 2026 01:35
9832b64 to
6613bc0
Compare
This was referenced Sep 12, 2026
Also applies ruff format to test_shiny_module.py (two of the three spots were added in this stack; the third is pre-existing).
…data_source=) behavior Parity with the R tests in #306: unnamed registration replaces the config-time table without cleaning it up, an explicit table_name adds alongside it, and the registry is shared and cumulative across sessions.
cpsievert
added this pull request to stack #307
September 12, 2026 16:09
This was referenced Sep 12, 2026
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.
Stacked on #303
Depends on #303 (don't clean up a table replaced via
server(data_source=)) — review that first. This PR's diff is just the greeting-race fix on top of it.The problem
When no explicit greeting is provided, one is generated by asking the LLM to describe the registered table(s). That generation is asynchronous — it happens after
.server()has already returned, whenever the chat UI actually needs to show a greeting.At that later point, greeting generation reads two things live: the list of table names to describe (
greeter.tables) and the QueryChat instance's data sources — both shared, mutable state. With per-session registration:.server(). Its greeting hasn't been generated yet — that happens later, once the chat UI mounts.greeter.tables/the data sources describe.The fix
.server()now captures a snapshot ofgreeter.tablessynchronously, at call time (alongside the data-sources snapshot it already captured for other reasons), and passes both through to a new privateQueryChatGreeter._generate_async_snapshot()used only by the Shiny module's greeting callback. The async greeting generation reads that snapshot instead of live state.This intentionally isn't exposed on the public
generate()/generate_async()/build_client()API — it's an internal fix for a race the per-session registration pattern introduces, not a new feature for users to reach for.Test plan
QueryChatGreeter._generate_async_snapshot(): an explicittables/data_sourcessnapshot is used instead of live state.mod_server(): the greeting callback calls_generate_async_snapshot()with the snapshot it was given, not the live greeter..server(): it passesgreeting_tables=list(self.greeter.tables)through tomod_server().add_table()+ per-sessionserver(data_source=)behavior (parity with fix(r): let $server(data_source=) survive a second session safely #306's R tests): unnamed registration replaces the config-time table without cleaning it up, an explicittable_nameadds alongside it, and the registry is shared and cumulative across sessions.