claude-threads 1.21.0 → 1.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/index.js +52 -25
- package/dist/mcp/mcp-server.js +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.21.1] - 2026-08-07
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **A respawning session can no longer be torn down by its own dying Claude process.** Two stacked races made `!cd` / `!permissions` / worktree respawns flaky (seen as the recurring `!cd should restart Claude CLI` failure on main's Integration Tests): a last event flushed by the dying process ran `resetSessionActivity`, which unconditionally flipped the session's `restarting` state back to `active` — so when the old process's exit landed, `handleExit` mistook it for the current process dying and did a full session teardown mid-restart. `resetSessionActivity` now leaves `restarting`/`cancelling` states alone, `handleExit` ignores exits from a CLI instance that is no longer the session's current one (the exit event can be delivered after the respawn already swapped in the new instance), and a successful respawn transitions to `active` explicitly instead of relying on the old exit's side effect. `ClaudeCli.kill()` gained an integration-test caller trace, mirroring the existing `sendMessage` one — attributing kills is the key question when debugging these races in CI logs.
|
|
12
|
+
- **`checkGitHubCli` unit tests no longer spawn the real `gh` CLI.** The two subprocess probes each carry a 5s timeout, overrunning bun's 5s per-test budget on a slow runner (the flaky `checkGitHubCli` timeout on main's CI). The exec call is now injectable and the tests cover all three outcomes (installed+authenticated, not installed, not authenticated) deterministically.
|
|
13
|
+
- **Integration: the `!cd` confirmation wait no longer matches the user's own message.** The loose `/changed|directory|\/tmp/i` pattern matched the `!cd /tmp` command itself, ending the test while the respawn was still in flight — `afterEach`'s `killAllSessions` then raced the restart and leaked an orphaned mock CLI into the next test.
|
|
14
|
+
|
|
8
15
|
## [1.21.0] - 2026-08-07
|
|
9
16
|
|
|
10
17
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -56341,6 +56341,12 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
56341
56341
|
const pid = proc.pid;
|
|
56342
56342
|
this.process = null;
|
|
56343
56343
|
this.log.debug(`Killing Claude process (pid=${pid})`);
|
|
56344
|
+
if (process.env.INTEGRATION_TEST === "1") {
|
|
56345
|
+
const stack = new Error().stack?.split(`
|
|
56346
|
+
`).slice(2, 7).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
|
|
56347
|
+
process.stderr.write(`[claude-cli kill pid=${pid}] | ${stack}
|
|
56348
|
+
`);
|
|
56349
|
+
}
|
|
56344
56350
|
return new Promise((resolve4) => {
|
|
56345
56351
|
this.log.debug("Sending first SIGINT");
|
|
56346
56352
|
proc.kill("SIGINT");
|
|
@@ -58526,7 +58532,10 @@ function resetSessionActivity(session) {
|
|
|
58526
58532
|
session.lastActivityAt = new Date;
|
|
58527
58533
|
session.timeoutWarningPosted = false;
|
|
58528
58534
|
session.lifecyclePostId = undefined;
|
|
58529
|
-
|
|
58535
|
+
const state = session.lifecycle.state;
|
|
58536
|
+
if (state !== "restarting" && state !== "cancelling") {
|
|
58537
|
+
transitionTo(session, "active");
|
|
58538
|
+
}
|
|
58530
58539
|
if (session.worktreeInfo?.worktreePath) {
|
|
58531
58540
|
updateWorktreeActivity(session.worktreeInfo.worktreePath, session.sessionId);
|
|
58532
58541
|
}
|
|
@@ -62641,9 +62650,9 @@ _Reported via claude-threads bug report feature_`);
|
|
|
62641
62650
|
function escapeShell(str2) {
|
|
62642
62651
|
return str2.replace(/"/g, "\\\"");
|
|
62643
62652
|
}
|
|
62644
|
-
function checkGitHubCli() {
|
|
62653
|
+
function checkGitHubCli(exec2 = execSync2) {
|
|
62645
62654
|
try {
|
|
62646
|
-
|
|
62655
|
+
exec2("gh --version", {
|
|
62647
62656
|
encoding: "utf-8",
|
|
62648
62657
|
timeout: 5000,
|
|
62649
62658
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -62656,7 +62665,7 @@ function checkGitHubCli() {
|
|
|
62656
62665
|
};
|
|
62657
62666
|
}
|
|
62658
62667
|
try {
|
|
62659
|
-
|
|
62668
|
+
exec2("gh auth status", {
|
|
62660
62669
|
encoding: "utf-8",
|
|
62661
62670
|
timeout: 5000,
|
|
62662
62671
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -68233,10 +68242,14 @@ async function createAndSwitchToWorktree(session, branch, username, options) {
|
|
|
68233
68242
|
appendSystemPrompt: await buildAppendSystemPrompt(session.platform, session.platformId, existing.path, session.threadId, session.startedBy, session.sessionAllowedUsers, options.appendSystemPrompt ?? "", options.githubEmailsStore, { omitSessionContext: !needsTitlePrompt, userAttribution: session.userAttribution })
|
|
68234
68243
|
};
|
|
68235
68244
|
session.messageManager?.clearClaudeSessionState();
|
|
68236
|
-
|
|
68237
|
-
session.claude
|
|
68238
|
-
|
|
68239
|
-
|
|
68245
|
+
const newClaude = new ClaudeCli(cliOptions);
|
|
68246
|
+
session.claude = newClaude;
|
|
68247
|
+
newClaude.on("event", (e) => options.handleEvent(session.sessionId, e));
|
|
68248
|
+
newClaude.on("exit", (code) => options.handleExit(session.sessionId, code, newClaude));
|
|
68249
|
+
newClaude.start();
|
|
68250
|
+
if (isSessionRestarting(session)) {
|
|
68251
|
+
transitionTo(session, "active");
|
|
68252
|
+
}
|
|
68240
68253
|
}
|
|
68241
68254
|
await options.updateSessionHeader(session);
|
|
68242
68255
|
await post(session, "success", `${fmt.formatBold("Joined existing worktree")} for branch ${fmt.formatCode(branch)}
|
|
@@ -68326,10 +68339,14 @@ ${fmt.formatItalic("Claude Code restarted in the worktree")}`);
|
|
|
68326
68339
|
appendSystemPrompt: await buildAppendSystemPrompt(session.platform, session.platformId, worktreePath, session.threadId, session.startedBy, session.sessionAllowedUsers, options.appendSystemPrompt ?? "", options.githubEmailsStore, { omitSessionContext: !needsTitlePrompt, userAttribution: session.userAttribution })
|
|
68327
68340
|
};
|
|
68328
68341
|
session.messageManager?.clearClaudeSessionState();
|
|
68329
|
-
|
|
68330
|
-
session.claude
|
|
68331
|
-
|
|
68332
|
-
|
|
68342
|
+
const newClaude = new ClaudeCli(cliOptions);
|
|
68343
|
+
session.claude = newClaude;
|
|
68344
|
+
newClaude.on("event", (e) => options.handleEvent(session.sessionId, e));
|
|
68345
|
+
newClaude.on("exit", (code) => options.handleExit(session.sessionId, code, newClaude));
|
|
68346
|
+
newClaude.start();
|
|
68347
|
+
if (isSessionRestarting(session)) {
|
|
68348
|
+
transitionTo(session, "active");
|
|
68349
|
+
}
|
|
68333
68350
|
}
|
|
68334
68351
|
await options.updateSessionHeader(session);
|
|
68335
68352
|
const shortWorktreePath = shortenPath(worktreePath, undefined, { path: worktreePath, branch });
|
|
@@ -69230,15 +69247,21 @@ async function restartClaudeSession(session, cliOptions, ctx, actionName) {
|
|
|
69230
69247
|
if (!cliOptions.resume) {
|
|
69231
69248
|
session.messageManager?.clearClaudeSessionState();
|
|
69232
69249
|
}
|
|
69233
|
-
|
|
69234
|
-
session.claude
|
|
69235
|
-
|
|
69236
|
-
|
|
69250
|
+
const newClaude = new ClaudeCli(cliOptions);
|
|
69251
|
+
session.claude = newClaude;
|
|
69252
|
+
newClaude.on("event", (e) => ctx.ops.handleEvent(session.sessionId, e));
|
|
69253
|
+
newClaude.on("exit", (code) => ctx.ops.handleExit(session.sessionId, code, newClaude));
|
|
69254
|
+
newClaude.on("rate-limit", (hit) => handleRateLimit(session, hit, ctx));
|
|
69237
69255
|
try {
|
|
69238
|
-
|
|
69256
|
+
newClaude.start();
|
|
69257
|
+
if (isSessionRestarting(session)) {
|
|
69258
|
+
transitionTo(session, "active");
|
|
69259
|
+
}
|
|
69239
69260
|
return true;
|
|
69240
69261
|
} catch (err) {
|
|
69241
|
-
|
|
69262
|
+
if (isSessionRestarting(session)) {
|
|
69263
|
+
transitionTo(session, "active");
|
|
69264
|
+
}
|
|
69242
69265
|
await logAndNotify(err, { action: actionName, session });
|
|
69243
69266
|
return false;
|
|
69244
69267
|
}
|
|
@@ -70351,7 +70374,7 @@ async function startSession(options, username, displayName, replyToPostId, platf
|
|
|
70351
70374
|
await ctx.ops.updateStickyMessage();
|
|
70352
70375
|
ctx.ops.startTyping(session);
|
|
70353
70376
|
claude.on("event", (e) => ctx.ops.handleEvent(sessionId, e));
|
|
70354
|
-
claude.on("exit", (code) => ctx.ops.handleExit(sessionId, code));
|
|
70377
|
+
claude.on("exit", (code) => ctx.ops.handleExit(sessionId, code, claude));
|
|
70355
70378
|
claude.on("rate-limit", (hit) => handleRateLimit(session, hit, ctx));
|
|
70356
70379
|
try {
|
|
70357
70380
|
claude.start();
|
|
@@ -70546,7 +70569,7 @@ Please start a new session.`), { action: "Post resume failure notification" });
|
|
|
70546
70569
|
ctx.ops.emitSessionAdd(session);
|
|
70547
70570
|
keepAlive.sessionStarted();
|
|
70548
70571
|
claude.on("event", (e) => ctx.ops.handleEvent(sessionId, e));
|
|
70549
|
-
claude.on("exit", (code) => ctx.ops.handleExit(sessionId, code));
|
|
70572
|
+
claude.on("exit", (code) => ctx.ops.handleExit(sessionId, code, claude));
|
|
70550
70573
|
claude.on("rate-limit", (hit) => handleRateLimit(session, hit, ctx));
|
|
70551
70574
|
try {
|
|
70552
70575
|
claude.start();
|
|
@@ -70634,7 +70657,7 @@ async function resumePausedSession(threadId, message, files, ctx, username) {
|
|
|
70634
70657
|
log31.warn(`Failed to resume session ${shortId}..., could not send message`);
|
|
70635
70658
|
}
|
|
70636
70659
|
}
|
|
70637
|
-
async function handleExit(sessionId, code, ctx) {
|
|
70660
|
+
async function handleExit(sessionId, code, ctx, source) {
|
|
70638
70661
|
const session = mutableSessions(ctx).get(sessionId);
|
|
70639
70662
|
const shortId = sessionId.substring(0, 8);
|
|
70640
70663
|
sessionLog6(session).debug(`handleExit called code=${code} isShuttingDown=${ctx.state.isShuttingDown}`);
|
|
@@ -70642,6 +70665,10 @@ async function handleExit(sessionId, code, ctx) {
|
|
|
70642
70665
|
log31.debug(`Session ${shortId}... not found (already cleaned up)`);
|
|
70643
70666
|
return;
|
|
70644
70667
|
}
|
|
70668
|
+
if (source && session.claude !== source) {
|
|
70669
|
+
sessionLog6(session).debug(`Ignoring exit from replaced Claude process`);
|
|
70670
|
+
return;
|
|
70671
|
+
}
|
|
70645
70672
|
if (isSessionRestarting(session)) {
|
|
70646
70673
|
sessionLog6(session).debug(`Restarting, skipping cleanup`);
|
|
70647
70674
|
transitionTo(session, "active");
|
|
@@ -71365,7 +71392,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
71365
71392
|
updateSessionHeader: (s) => this.updateSessionHeader(s),
|
|
71366
71393
|
updateStickyMessage: () => this.updateStickyMessage(),
|
|
71367
71394
|
handleEvent: (sid, e) => this.handleEvent(sid, e),
|
|
71368
|
-
handleExit: (sid, code) => this.handleExit(sid, code),
|
|
71395
|
+
handleExit: (sid, code, source) => this.handleExit(sid, code, source),
|
|
71369
71396
|
killSession: (tid) => this.killSession(tid),
|
|
71370
71397
|
shouldPromptForWorktree: (s) => this.shouldPromptForWorktree(s),
|
|
71371
71398
|
postWorktreePrompt: (s, r) => this.postWorktreePrompt(s, r),
|
|
@@ -71469,8 +71496,8 @@ class SessionManager extends EventEmitter4 {
|
|
|
71469
71496
|
session.messageManager.handleEvent(event);
|
|
71470
71497
|
handleEventPostProcessing(session, event, this.getContext());
|
|
71471
71498
|
}
|
|
71472
|
-
async handleExit(sessionId, code) {
|
|
71473
|
-
await handleExit(sessionId, code, this.getContext());
|
|
71499
|
+
async handleExit(sessionId, code, source) {
|
|
71500
|
+
await handleExit(sessionId, code, this.getContext(), source);
|
|
71474
71501
|
}
|
|
71475
71502
|
startTyping(session) {
|
|
71476
71503
|
const wasTyping = session.timers.typingTimer !== null;
|
|
@@ -71985,7 +72012,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
71985
72012
|
worktreeMode: this.worktreeMode,
|
|
71986
72013
|
permissionTimeoutMs: this.limits.permissionTimeoutSeconds * 1000,
|
|
71987
72014
|
handleEvent: (tid, e) => this.handleEvent(tid, e),
|
|
71988
|
-
handleExit: (tid, code) => this.handleExit(tid, code),
|
|
72015
|
+
handleExit: (tid, code, source) => this.handleExit(tid, code, source),
|
|
71989
72016
|
updateSessionHeader: (s) => this.updateSessionHeader(s),
|
|
71990
72017
|
flush: async (s) => {
|
|
71991
72018
|
if (s.messageManager) {
|
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -56263,6 +56263,12 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
56263
56263
|
const pid = proc.pid;
|
|
56264
56264
|
this.process = null;
|
|
56265
56265
|
this.log.debug(`Killing Claude process (pid=${pid})`);
|
|
56266
|
+
if (process.env.INTEGRATION_TEST === "1") {
|
|
56267
|
+
const stack = new Error().stack?.split(`
|
|
56268
|
+
`).slice(2, 7).join(" > ").replace(/\s+at\s+/g, " < ") ?? "?";
|
|
56269
|
+
process.stderr.write(`[claude-cli kill pid=${pid}] | ${stack}
|
|
56270
|
+
`);
|
|
56271
|
+
}
|
|
56266
56272
|
return new Promise((resolve5) => {
|
|
56267
56273
|
this.log.debug("Sending first SIGINT");
|
|
56268
56274
|
proc.kill("SIGINT");
|
package/package.json
CHANGED