Conversation
shikokuchuo
left a comment
There was a problem hiding this comment.
From a conversations with @hadley, we could tweak the UI further so that:
- Starting a new app automatically stops the old one
- Have it automatically run non-blocking if used by an agent (via env vars)
|
I like the model of automatically stopping the previously running app as it matches the existing model pretty closely; it just skips the step of having to Ctrl + C to quit the current app. I would hope that eventually nonblocking mode becomes the default, but defaulting to only during LLM usage would be a good place to start. |
|
Thanks @hadley, starting a new app now automatically stops the old one, and non-blocking is the default for LLMs. |
5573f6b to
ce11abe
Compare
| #' @return If `blocking = TRUE`, returns the value passed to [stopApp()], or | ||
| #' throws an error if the app was stopped with an error. If `blocking = FALSE`, | ||
| #' returns a `ShinyAppHandle` object with methods `stop()`, `status()`, | ||
| #' `url()`, and `result()`. The `status()` method returns `"running"`, | ||
| #' `"success"`, or `"error"`. The `result()` method throws an error if called | ||
| #' while running, or re-throws the error if the app stopped with an error. |
There was a problem hiding this comment.
I am worried about the goals of the future desired behavior.
I believe we need a different function for non-blocking runApp. Returning vastly different objects given a new parameter isn't right.
- Non-blocking for LLMs only
- On CRAN, LLMS already know to run in the background. If this was switched to be non-blocking, the app won't run as expected. To mitigate this, we'd need to tell the LLM to read the docs... which it could read about the new method instead.
- If the future goal is to make non-blocking the default behavior, many programs (Posit Connect included) use
runApp()as is. To add a parameter to make it non-blocking will cause pain for all existing Connect users who do not have the bleeding edge version. This will take years to recover from as updating Connect versions is a non-trivial process for admins.
There was a problem hiding this comment.
If the goal is to expose state to AI, maybe startApp() as it follows {httpuv}'s startServer() ?
It would need doc updates in stopApp() in that they are unrelated.
This would be very painful for pro products (Connect admins) that use For LLM usage to be non-blocking, my interactions with running a shiny app is that the app is launched in a background process that the LLM watches. If the Shiny process then suddenly became non-blocking (because it is being run in an LLM), the R process would quit after the initial tick. User's wouldn't be able to replicate the behavior running it interactively. (... Step 4. The pro products would need conditional behavior based on the package version. So we might as well use another method due to properly support the drastically different behavior and return type. Proposal: |
More painful than having the hosting environments set
What about running an app by printing a |
I thought of this as well. It's convenient to be able to modify the behaviour of the implicit Also in this case, people call it primarily for the side effect of running the App. What |
|
@cpsievert just to update that I tested the Rstudio |
|
Let's move this forward on the following basis (as discussed in today's team meeting):
This has the following advantages:
Noting that @schloerke continues to prefer 2 separate functions. @cpsievert for you to approve this PR. Thanks! |
Motivation
AI coding agents (such as Claude Code) need to programmatically start, test, and stop Shiny apps without blocking the R console. Currently,
runApp()blocks until the app is stopped, making it cumbersome for agents to:Changes
Adds a blocking parameter to
runApp(),runExample(), andrunGadget():Implementation
ShinyAppHandle(R6 class): Returned whenblocking = FALSE. Provides methods for lifecycle management and accessing the app's return value.serviceAsync(): Runs the httpuv event loop via later callbacks (1ms delay) instead of a blocking while loop.createCleanup(): Consolidated cleanup logic shared between blocking and non-blocking modes.options(shiny.blocking = FALSE).Safeguards