Skip to content

Support session init hooks (mostly motivated by R)#12312

Merged
jennybc merged 12 commits intomainfrom
feature/frontend-ready-notification
Apr 1, 2026
Merged

Support session init hooks (mostly motivated by R)#12312
jennybc merged 12 commits intomainfrom
feature/frontend-ready-notification

Conversation

@jennybc
Copy link
Copy Markdown
Member

@jennybc jennybc commented Mar 6, 2026

Addresses #9763
Addresses posit-dev/ark#697

Requires this companion PR in ark: posit-dev/ark#1088

Summary

Adds infrastructure for R session init hooks that fire after the frontend is fully connected to the runtime. This enables a user or admin to store code in .Rprofile that drives the UI or requires two-way communication with the frontend.

Two main changes:

  1. comm_open data carries { console_width, start_type } when the UI comm opens. The runtime uses console_width immediately (no separate RPC needed). start_type is informational/logging. (This is not load-bearing for session init hooks. Rather, it's a little goodie I kept from my second approach to this problem. We had hoped to fire session init hooks during UI comm opening, but it just doesn't work. It's too early. But this does feel like the right way to inform the backend of the initial console width.)

  2. frontend_ready RPC is sent after comm_open succeeds. The runtime uses this signal to fire session hooks. This is a separate step from comm_open because the hooks may need to make RPCs back to the frontend, which requires the comm to be fully registered first. (This is basically a return to my first approach to the problem. Even more work was required in ark and is described in that PR.)

Start type mapping

RuntimeStartMode is an internal Positron enum. start_type is the external value sent to the runtime via the frontend_ready RPC, which the runtime uses to decide which hooks to fire.

RuntimeStartMode (Positron internal) start_type (runtime-facing) When
Starting "new" New runtime session, in a new or existing IDE session
Switching "new" Notebook switches to a different kernel
Restarting "restart" Existing runtime session restarted within an IDE session
Reconnecting "reconnect" Frontend reconnects to an existing runtime session (e.g. window reload, resuming a session on Workbench)

Changes

  • ui-backend-openrpc.json: Added frontend_ready RPC with start_type param. Ran codegen (npx tsx generate-comms.ts ui), which updated positronUiComm.ts, ui_comm.py, and ui_comm.rs (in ark).
  • languageRuntimeUiClient.ts: Added frontendReady() wrapper on UiClientInstance (the generated method lives on PositronUiComm but callers go through UiClientInstance).
  • activeRuntimeSession.ts: Tracks startMode on ActiveRuntimeSession. New getStartType() maps it to runtime-facing values. startUiClientImpl() passes { console_width, start_type } in comm_open data (previously {}). Sends frontendReady(startType) after comm setup completes.
  • runtimeSession.ts: Threads startMode through attachToSession() and updates it when a restart is detected.
  • positron-r/session.ts: Removed setConsoleWidth() (now handled via comm_open data on the ark side).

Background

This enables runtime-side session init hooks, inspired by RStudio's rstudio.sessionInit. See the ark companion PR for the runtime-side implementation. Relevant docs re: RStudio:

What sorts of things do people do in rstudio.sessionInit? Special thanks to @jthomasmock for helping gather this list. It's worth noting that session init hooks are much more relevant in a Workbench/enterprise context than desktop/individual user.

  • Use rstudioapi to do something that drives the UI, such as rstudioapi::navigateToFile(). This has been my main manual test case. However, since it's "fire and forget", it can't be the only test case.
  • Use rstudioapi to trigger two-way communication with the UI, such as rstudioapi::getActiveProject(). This turns out to be a very important use case to test.
  • Emit a welcome message that is correctly formatted for the actual console width (versus a default of 80). Console width is not recognized at R session startup r-lib/cli#578
  • Display a legal compliance message to users in new sessions.
  • Reinstall R packages, e.g. via renv::restore(), after an OS upgrade. Note that the hook itself does not detect the OS upgrade. But if the upgrade has been detected, the hook gets registered. This example is from Posit Cloud.
  • Defer package load that was conflicting with other startup events (package installation). This example is from RStudio itself. IDE keeps getting stuck in a loop to restart R when trying to install a package that is already installed rstudio/rstudio#2656
  • Switch the RStudio theme (light vs. dark) based on the time of day. From rsthemes recommended usage. Use rstudio.sessionInit hook instead of later gadenbuie/rsthemes#8
  • Automatically load sparklyr and establish a Spark connection to a YARN cluster.
  • Append an internal package repository or library path in a way that absolutely can't be clobbered by RStudio itself.

Release Notes

New Features

  • positron.session_init and positron.session_reconnect are new hooks that are executed in the R session, once R startup is complete and Positron is fully operational and able to handle requests that pertain to the UI, such as rstudioapi calls. This duo is intended to be analogous to RStudio's rstudio.sessionInit. A positron.session_init hook function should accept a start_type parameter, with possible values "new" or "restart". A positron.session_reconnect hook takes no input.

Bug Fixes

  • N/A

QA Notes

Temporarily make this your .Rprofile (requires ark built from companion PR) and make sure the current folder has a DESCRIPTION file.

Click to expand
message("Plain message, before session init hooks are defined")
message("cli thinks the width is ", cli::console_width())

setHook(
  "positron.session_init",
  function(start_type) {
    message(
      "positron.session_init called with `start_type = '",
      start_type,
      "'`"
    )
    message("cli thinks the width is ", cli::console_width())

    switch(
      start_type,
      new = message("Welcome to Positron ", rstudioapi::getVersion()),
      restart = message(
        "Welcome BACK to Positron ",
        rstudioapi::getVersion()
      ),
      message("Unknown start_type: ", start_type)
    )

    open_this <- "DESCRIPTION"
    if (file.exists(open_this)) {
      message("Opening '", open_this, "'")
      rstudioapi::navigateToFile(open_this)
    }

    message("Active project: ", rstudioapi::getActiveProject())
  },
  action = "append"
)

setHook(
  "positron.session_reconnect",
  function(...) {
    message("Reconnected to Positron ", rstudioapi::getVersion())
    message("Active project: ", rstudioapi::getActiveProject())
    message("cli thinks the width is ", cli::console_width())
  },
  action = "append"
)

message("Another plain message, after session init hooks are defined")

In each case, DESCRIPTION should be opened in the editor via rstudioapi::navigateToFile(). Here's what you should see in the Console, up to whatever your console width and active project are.

New session (start_type = "new"):

Plain message, before session init hooks are defined
cli thinks the width is 80
Another plain message, after session init hooks are defined
positron.session_init called with `start_type = 'new'`
cli thinks the width is 148
Welcome to Positron 2026.4.0
Opening 'DESCRIPTION'
Active project: /Users/jenny/work/readr

Restart R (start_type = "restart"):

Plain message, before session init hooks are defined
cli thinks the width is 80
Another plain message, after session init hooks are defined
positron.session_init called with `start_type = 'restart'`
cli thinks the width is 148
Welcome BACK to Positron 2026.4.0
Opening 'DESCRIPTION'
Active project: /Users/jenny/work/readr

Reload window (start_type = "reconnect"):

Notice that only the hook is being run, not the top-level .Rprofile statements.

Reconnected to Positron 2026.4.0
Active project: /Users/jenny/work/readr
cli thinks the width is 148

TODOs:

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 6, 2026

E2E Tests 🚀
This PR will run tests tagged with: @:critical

readme  valid tags

@jennybc jennybc force-pushed the feature/frontend-ready-notification branch from 9cbc7df to 536a796 Compare March 10, 2026 21:58
@jennybc jennybc marked this pull request as ready for review March 10, 2026 22:01
@jennybc jennybc force-pushed the feature/frontend-ready-notification branch 2 times, most recently from 4a244e3 to 9c2f8a7 Compare March 19, 2026 01:30
@jennybc jennybc changed the title Inform the backend that the frontend is ready Pass initial state (console width, start type) in UI comm open Mar 19, 2026
@jennybc jennybc force-pushed the feature/frontend-ready-notification branch from 9c2f8a7 to d2d35f4 Compare March 24, 2026 23:59
jennybc added 8 commits March 31, 2026 15:10
The opening of the UI comm signals to the runtime that the frontend is ready. Include `console_width` and `start_type` in the `comm_open` data. Allows the runtime to set the console width (existing behaviour) and to fire session init hooks with the appropriate data (new behaviour).
@jennybc jennybc force-pushed the feature/frontend-ready-notification branch from d2d35f4 to 6655e7b Compare March 31, 2026 22:11
@jennybc jennybc changed the title Pass initial state (console width, start type) in UI comm open Support R session init hooks (positron.session_init, positron.session_reconnect) Mar 31, 2026
@jennybc jennybc changed the title Support R session init hooks (positron.session_init, positron.session_reconnect) Support session init hooks (mostly motivated by R) Apr 1, 2026
@jmcphers jmcphers self-requested a review April 1, 2026 14:53
jmcphers
jmcphers previously approved these changes Apr 1, 2026
Copy link
Copy Markdown
Collaborator

@jmcphers jmcphers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks and works great for me. I'm seeing all 3 start types (new, restart, reconnect) fire correctly, and verified that console width is still getting tracked correctly at attach and resize.

jennybc added a commit to posit-dev/ark that referenced this pull request Apr 1, 2026
#1088)

Addresses posit-dev/positron#9763
Closes #697

Companion PR in Positron:
posit-dev/positron#12312 (see there for full
context, background, and QA instructions)

## Summary

Adds R session init hooks that fire after the frontend is fully
connected, so that rstudioapi functions (including those that make RPCs
back to the frontend, like `getActiveProject()`) work from hook code.

### Hook design (two hooks)

- **`positron.session_init(start_type)`** -- fires on new sessions and
restarts
  - `start_type = "new"` for new sessions
  - `start_type = "restart"` for restarted sessions
- **`positron.session_reconnect()`** -- fires on reconnects only (e.g.
window reload)

These are analogous to RStudio's
[`rstudio.sessionInit`](https://rstudio.github.io/rstudioapi/articles/r-session.html).
Users register hooks in `.Rprofile` via `setHook()`.

### The originator problem and fix

rstudioapi functions like `getActiveProject()` make synchronous RPCs
back to the frontend. These RPCs require a Jupyter message originator so
replies can be properly parented. Previously, only `ExecuteRequest`
handlers set an originator -- `comm_msg` handlers did not.

Since the `frontend_ready` signal arrives as a `comm_msg` on the UI
comm, hooks fired from that context couldn't make frontend RPCs. This PR
threads the originator from the `comm_msg` Jupyter message through to
the R thread:

- **amalthea**: `handle_comm_msg` trait method gains an `originator`
parameter; `shell.rs` creates `Originator` from the `comm_msg` request
header
- **ark**: `KernelRequest::CommMsg` carries the originator; `Console`
temporarily sets `comm_msg_originator` during `comm_handle_msg`;
`call_frontend_method()` falls back to `comm_msg_originator` when there
is no `active_request`

### Other changes

- **`comm_open` data**: `handle_comm_open` trait method gains a `data`
parameter. The UI comm deserializes this into a typed `UiCommOpenData`
struct and reads `console_width` from it to set immediately (replacing
the old `setConsoleWidth` RPC from `positron-r`).
- **`frontend_ready` handler**: Dispatches to
`.ps.run_session_init_hooks(start_type)` or
`.ps.run_session_reconnect_hooks()` based on `start_type`.
- **`hooks.R`**: Two new internal functions with tryCatch error
isolation per hook.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lionel Henry <lionel.hry@proton.me>
Signed-off-by: Jennifer (Jenny) Bryan <jenny.f.bryan@gmail.com>
@jennybc jennybc merged commit e3c021b into main Apr 1, 2026
25 of 26 checks passed
@jennybc jennybc deleted the feature/frontend-ready-notification branch April 1, 2026 23:27
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants