Skip to content

Commit c2a2a07

Browse files
IMNMVclaude
andcommitted
Stop the Windows liveness check from killing R sessions
ClaudeR 0.13.2. cleanup_stale_discovery_files() probed a recorded pid with tools::pskill(pid, signal = 0). On Unix that maps to kill(pid, 0) and is a safe existence test, which is what the comment claimed. On Windows it is not: ?tools::pskill states only SIGINT and SIGTERM exist there and that pskill always uses TerminateProcess. The probe therefore terminated the session it was asking about, and because the kill succeeded the file was judged live and left behind, so agents were then routed to a dead port. It ran on every Start Server, so a Windows user with two RStudio sessions killed one by starting the other. New pid_is_alive() never signals on Windows; it asks tasklist, which ships with the OS and needs no extra package. Unix keeps kill(pid, 0). Also: write_discovery_file() overwrote the discovery file of a different live session registered under the same name, leaving agents with a stale port and a token that no longer matched. Port reuse was already reported loudly; this was silent. It now warns and names the conflicting pid and port. R CMD check: Status OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 64ee723 commit c2a2a07

3 files changed

Lines changed: 41 additions & 4 deletions

File tree

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: ClaudeR
22
Title: R Integration for Claude AI
3-
Version: 0.13.1
3+
Version: 0.13.2
44
Authors@R: person("Nykko", "Vitali", email = "nykvt@icloud.com", role = c("aut", "cre"))
55
Description: Connects RStudio with Claude AI to enable interactive coding sessions.
66
License: MIT + file LICENSE

R/ui.R

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ write_discovery_file <- function(session_name, port, token) {
6565
started_at = format(Sys.time(), "%Y-%m-%dT%H:%M:%S")
6666
)
6767
f <- file.path(d, paste0(session_name, ".json"))
68+
# Two live sessions sharing a name would silently clobber each other's
69+
# discovery file, and agents would then be routed to one of them holding the
70+
# other's token. Port reuse is already caught loudly; this was not.
71+
if (file.exists(f)) {
72+
other <- tryCatch(jsonlite::fromJSON(f), error = function(e) NULL)
73+
if (!is.null(other) && !identical(as.integer(other$pid), Sys.getpid()) &&
74+
pid_is_alive(other$pid)) {
75+
warning(sprintf(paste0("Another live R session is already registered as '%s' ",
76+
"(pid %s, port %s). Give this session a different name, ",
77+
"or agents may be routed to the wrong one."),
78+
session_name, other$pid, other$port), call. = FALSE)
79+
}
80+
}
6881
jsonlite::write_json(info, f, auto_unbox = TRUE, pretty = TRUE)
6982
try(Sys.chmod(f, mode = "0600"), silent = TRUE)
7083
}
@@ -74,16 +87,38 @@ remove_discovery_file <- function(session_name) {
7487
if (file.exists(f)) file.remove(f)
7588
}
7689

90+
# Is a process alive? Must never kill it.
91+
#
92+
# tools::pskill(pid, signal = 0) is the usual idiom, and it is safe on Unix
93+
# where it maps to kill(pid, 0). On Windows it is NOT: ?tools::pskill states
94+
# that only SIGINT and SIGTERM exist there and that pskill "will always use the
95+
# Windows system call TerminateProcess". Using it as a liveness probe therefore
96+
# terminated the very session it was asking about, and returned TRUE for the
97+
# kill, so the discovery file was then judged live and left behind.
98+
pid_is_alive <- function(pid) {
99+
pid <- suppressWarnings(as.integer(pid))
100+
if (is.na(pid) || pid <= 0) return(FALSE)
101+
if (.Platform$OS.type == "windows") {
102+
# tasklist ships with Windows, so this needs no extra package.
103+
out <- tryCatch(
104+
suppressWarnings(system2("tasklist",
105+
c("/FI", shQuote(sprintf("PID eq %d", pid)), "/NH"),
106+
stdout = TRUE, stderr = NULL)),
107+
error = function(e) character(0)
108+
)
109+
return(any(grepl(paste0("\\b", pid, "\\b"), out)))
110+
}
111+
isTRUE(tryCatch(tools::pskill(pid, signal = 0), error = function(e) FALSE))
112+
}
113+
77114
cleanup_stale_discovery_files <- function() {
78115
d <- discovery_dir()
79116
if (!dir.exists(d)) return(invisible(NULL))
80117
files <- list.files(d, pattern = "\\.json$", full.names = TRUE)
81118
for (f in files) {
82119
tryCatch({
83120
info <- jsonlite::fromJSON(f)
84-
# signal = 0 checks if PID exists without killing it
85-
pid_alive <- tools::pskill(info$pid, signal = 0)
86-
if (!isTRUE(pid_alive)) file.remove(f)
121+
if (!pid_is_alive(info$pid)) file.remove(f)
87122
}, error = function(e) {
88123
# Corrupted file, remove it
89124
file.remove(f)

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ claudeAddin()
5050
<details>
5151
<summary><b>Recent Updates</b> (click to expand)</summary>
5252

53+
- **Windows session-liveness fix (R 0.13.2).** Checking whether a recorded R session was still alive used `tools::pskill(pid, signal = 0)`. That is the standard idiom on Unix, but on Windows `pskill` always calls `TerminateProcess` regardless of signal, so the check killed the session it was asking about, then reported it as alive and left the stale discovery file in place. Starting a server could therefore terminate another RStudio session and leave agents routed to a dead port. Liveness is now probed without signalling. Separately, registering a session whose name is already held by a different live session now warns instead of silently overwriting its discovery file, which had left agents holding the wrong port and token.
54+
5355
- **Unified console logging (R 0.13.0).** The session log recorded what the agent ran, but not what you ran, so asking an agent to explain an error you hit in the console meant re-running the work through it. Tick "Also log my own console commands" under Logging and your console activity joins the same file, tagged by who ran what: the command, its printed result, and any warnings, messages, or errors. "Read the last 100 lines of the log" is now enough for an agent to see both sides of the session. Off by default; toggle it in the addin or call `start_console_logging()` / `stop_console_logging()`.
5456

5557
- **Editor tools fixed, plus approve-before-apply edits (R 0.12.5 / clauder-mcp 0.14.4).** From two user reports. `modify_code_section` and `insert_text` now save to disk by default and report `saved_to_disk`, so an agent no longer believes it wrote a file when the change sat unsaved in the buffer. Bounded replacements may change the line count (the old equality constraint is gone). Both tools accept a `path` to target a specific file, open and focus it, and refuse to edit a different document instead of failing silently. `get_active_document` now reports the path, the document id, and whether the buffer differs from disk, so buffer state and file state stop being confused for each other. New `suggest_edit` tool: the agent proposes a change and waits for the user to approve it, using `rstudioapi::showEditSuggestion()` when the installed rstudioapi provides it, and otherwise staging the edit unsaved so the user accepts by saving or rejects with undo.

0 commit comments

Comments
 (0)