R Shiny Runner with Log Monitoring
Launch a Shiny app, open it in a browser via Playwright, follow free-form instructions autonomously, and monitor server logs after every action.
Prerequisites: Playwright MCP plugin must be installed and configured.
When to Use
- •User asks to run/start/launch a Shiny app and interact with it
- •User provides instructions like
/rshiny-run "create new project from scratch" - •You need to test Shiny UI behavior with live error detection
Runtime State
All runtime artifacts are stored in ~/.claude/rshiny-state/:
- •
shiny.log- server log output (created fresh each run) - •
port.txt- the random port Shiny chose (persists across sessions for/rshiny-stopand/rshiny-status)
Workflow
Step 1: Ensure State Directory Exists
mkdir -p ~/.claude/rshiny-state
Step 2: Cleanup Previous Run
Read the previous port from ~/.claude/rshiny-state/port.txt (if it exists) and kill any process on it:
PORT=$(cat ~/.claude/rshiny-state/port.txt 2>/dev/null) if [ -n "$PORT" ]; then PID=$(lsof -ti:$PORT) && kill -15 $PID 2>/dev/null; sleep 2 PID=$(lsof -ti:$PORT) && kill -9 $PID 2>/dev/null; sleep 1 fi
Step 3: Launch Shiny App (random port)
Truncate the log file, then start the app in the background. Do NOT specify a port - let Shiny pick a random available one:
> ~/.claude/rshiny-state/shiny.log
Then use the Bash tool with run_in_background: true:
TERM=dumb R --no-save --no-restore -e "shiny::runApp('.', launch.browser = FALSE)" >> ~/.claude/rshiny-state/shiny.log 2>&1
- •
TERM=dumbprevents ANSI escape codes at the source. - •Shell redirection works with
run_in_backgroundbecause the shell processes>>before the tool captures output.
Step 4: Extract Port from Log + Wait for Readiness
Shiny prints Listening on http://127.0.0.1:XXXX when ready. Poll the log for this line (up to 90 seconds for large apps):
for i in {1..45}; do
PORT=$(grep -oE 'Listening on http://127\.0\.0\.1:[0-9]+' ~/.claude/rshiny-state/shiny.log 2>/dev/null | grep -oE '[0-9]+$')
if [ -n "$PORT" ]; then
echo "$PORT" > ~/.claude/rshiny-state/port.txt
echo "APP_READY on port $PORT"; exit 0
fi
sleep 2
done
echo "APP_TIMEOUT"; exit 1
If APP_TIMEOUT: read shiny.log and report the startup error to the user. Stop.
Note: Uses grep -oE (extended regex) instead of grep -oP (Perl regex) for macOS compatibility.
Step 5: Open Browser
- •Read the port:
cat ~/.claude/rshiny-state/port.txt - •Use
browser_navigatetohttp://localhost:<PORT> - •Use
browser_wait_forwith a known UI element (app title or tab) to confirm Shiny has fully initialized - •Take initial
browser_snapshotto confirm app loaded
Step 6: Instruction Loop
If the user provided instructions (e.g., "create new project from scratch"), execute an autonomous interaction loop. If no instructions provided, just launch, navigate, snapshot, and report - the app stays running for manual interaction.
The loop runs for up to 100 iterations. After each browser action, wait 1-2 seconds for Shiny reactivity to settle, then check both server logs and browser console for errors. If the same action fails 3 times in a row, stop and report. On hard errors (server crash, disconnect), abort the loop and report what was accomplished.
Available Playwright actions (full vocabulary):
- •
browser_snapshot- read current UI state (do this before every decision) - •
browser_click- click buttons, links, tabs (supportsbutton: "right"for context menus) - •
browser_type/browser_fill_form- fill text inputs - •
browser_select_option- select from dropdowns - •
browser_press_key- keyboard actions (Enter, Escape, Tab, arrow keys, Ctrl+S) - •
browser_wait_for- wait for text to appear or disappear (usetextGonefor loading spinners) - •
browser_file_upload- upload files when encountering file input elements - •
browser_handle_dialog- dismiss JS alert/confirm/prompt dialogs - •
browser_drag- drag and drop operations - •
browser_hover- hover for tooltips or menus - •
browser_take_screenshot- visual verification when snapshot isn't enough - •
browser_console_messages- check for client-side JS errors - •
browser_tabs- manage browser tabs (useaction: "close"for cleanup)
Per-iteration flow:
- •
browser_snapshotto read current UI state - •Disconnect check: if snapshot shows "Disconnected from server" or "Connection lost", abort and report
- •Dialog check: if a JS dialog is blocking, use
browser_handle_dialogto dismiss it - •Analyze snapshot against user's instruction - what's done, what's next, is it complete?
- •If complete, break and report
- •Execute the next Playwright action
- •Wait 1-2 seconds for Shiny reactivity to settle (
browser_wait_forwithtime: 1ortime: 2) - •Check server logs: read
~/.claude/rshiny-state/shiny.logfrom last-read line forward (incremental). Scan new lines for errors:- •Hard errors (abort loop):
"Error in ","Error:","Traceback","Shiny application error","disconnected from server","fatal","Execution halted" - •Warnings (log and continue, mention in final report):
"Warning:" - •Informational (ignore):
"Listening on", deprecation notices
- •Hard errors (abort loop):
- •Check browser console:
browser_console_messageswithlevel: "warning". Flag"Uncaught","TypeError","WebSocket"errors. - •Brief progress note: "Step N: [action taken], [log status]"
Completion heuristics - the instruction is likely complete when:
- •A success toast/notification appears after a form submission
- •The UI returns to a "home" or list view after the requested action
- •The expected outcome is visible in the snapshot (e.g., new project appears in list)
Step 7: Report
- •Summary of actions taken (count and key steps)
- •Any errors or warnings found in logs
- •Final screenshot for visual confirmation
- •App status (still running on port from
port.txt) - •"Use
/rshiny-stopto shut down,/rshiny-logsto view server output"
Key Behaviors
- •Idempotent: re-running kills existing app first
- •Log-aware: every browser action triggers both server log and browser console checks
- •Autonomous: interprets natural language instructions and decides Playwright actions from snapshots
- •Bounded: max 100 iterations, 3-failure circuit breaker, abort on hard errors
- •Settle-aware: waits for Shiny reactivity between actions
Common Mistakes
| Mistake | Fix |
|---|---|
Using shell & for background | Use Bash tool's run_in_background: true parameter |
| Specifying a port in runApp | Let Shiny pick a random port, extract from log |
| Navigating before app is ready | Wait for "Listening on" line in log before proceeding |
Forgetting launch.browser = FALSE | Always set it to prevent duplicate browser windows |
Using browser_close to close tab | Use browser_tabs with action: "close" instead |
| Skipping log check after actions | Always read shiny.log after every browser action |
| Not waiting for reactivity | Wait 1-2 seconds after each action before next snapshot |
| Hardcoding element refs | Refs change between snapshots; always get fresh snapshot |
Using grep -oP on macOS | Use grep -oE with two-stage extraction instead |