Skip to content

fix(py): greeting reflects the session that registered its table - #304

Merged
cpsievert merged 3 commits into
fix/py-server-cleanup-safetyfrom
fix/py-server-greeting-snapshot
Sep 12, 2026
Merged

fix(py): greeting reflects the session that registered its table#304
cpsievert merged 3 commits into
fix/py-server-cleanup-safetyfrom
fix/py-server-greeting-snapshot

Conversation

@cpsievert

@cpsievert cpsievert commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

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:

qc = QueryChat(None, table_name="orders")

def server(input, output, session):
    conn = get_per_user_connection(session)
    qc.server(data_source=conn)
  • Session A registers its table and returns from .server(). Its greeting hasn't been generated yet — that happens later, once the chat UI mounts.
  • Session B registers its table in the meantime, changing what greeter.tables/the data sources describe.
  • Session A's greeting is then generated — and describes session B's table, not session A's.

The fix

.server() now captures a snapshot of greeter.tables synchronously, at call time (alongside the data-sources snapshot it already captured for other reasons), and passes both through to a new private QueryChatGreeter._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

  • New tests on QueryChatGreeter._generate_async_snapshot(): an explicit tables/data_sources snapshot is used instead of live state.
  • New test on mod_server(): the greeting callback calls _generate_async_snapshot() with the snapshot it was given, not the live greeter.
  • New test on .server(): it passes greeting_tables=list(self.greeter.tables) through to mod_server().
  • New tests locking in mixed config-time add_table() + per-session server(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 explicit table_name adds alongside it, and the registry is shared and cumulative across sessions.

This comment was marked as resolved.

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.

This comment was marked as resolved.

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
cpsievert added this pull request to stack #307 September 12, 2026 16:09
@cpsievert
cpsievert merged commit cc45ad6 into main Sep 12, 2026
7 checks passed
@cpsievert
cpsievert deleted the fix/py-server-greeting-snapshot branch September 12, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants