claude-threads 0.33.1 → 0.33.3

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 CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.33.3] - 2026-01-04
11
+
12
+ ### Fixed
13
+ - **Graceful shutdown sends two SIGINTs** - Claude CLI requires two Ctrl+C presses to exit in interactive mode. Updated kill() to send two SIGINTs (100ms apart) before falling back to SIGTERM after 2 seconds.
14
+
15
+ ## [0.33.2] - 2026-01-04
16
+
17
+ ### Fixed
18
+ - **Session resume "No conversation found" errors** - Fixed issue where cancelled sessions would fail to resume with "No conversation found with session ID" error. Root cause: sessions were persisted before Claude had a chance to save the conversation.
19
+ - **Graceful session termination** - When killing a session (cancel, !stop, etc.), Claude now gets 2 seconds to save the conversation (SIGINT then SIGTERM) instead of being killed immediately.
20
+ - **Detect invalid session IDs immediately** - Sessions with "No conversation found" errors are now recognized as permanent failures and removed from persistence immediately, instead of retrying 3 times.
21
+ - **User notification for early exits** - When a session ends before Claude responds, the user is now notified: "Session ended before Claude could respond. Please start a new session."
22
+
23
+ ### Changed
24
+ - **Delayed session persistence** - Sessions are only persisted after Claude has actually responded (first `assistant` or `tool_use` event), preventing dangling session records that can't be resumed.
25
+
10
26
  ## [0.33.1] - 2026-01-04
11
27
 
12
28
  ### Fixed
package/dist/index.js CHANGED
@@ -15282,6 +15282,10 @@ function extractAndUpdatePullRequest(text, session, ctx) {
15282
15282
  function handleEvent(session, event, ctx) {
15283
15283
  session.lastActivityAt = new Date;
15284
15284
  session.timeoutWarningPosted = false;
15285
+ if (!session.hasClaudeResponded && (event.type === "assistant" || event.type === "tool_use")) {
15286
+ session.hasClaudeResponded = true;
15287
+ ctx.ops.persistSession(session);
15288
+ }
15285
15289
  if (event.type === "assistant") {
15286
15290
  const msg = event.message;
15287
15291
  let hasSpecialTool = false;
@@ -16084,6 +16088,9 @@ class ClaudeCli extends EventEmitter2 {
16084
16088
  if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
16085
16089
  return true;
16086
16090
  }
16091
+ if (stderr.includes("No conversation found with session ID")) {
16092
+ return true;
16093
+ }
16087
16094
  return false;
16088
16095
  }
16089
16096
  getPermanentFailureReason() {
@@ -16091,12 +16098,32 @@ class ClaudeCli extends EventEmitter2 {
16091
16098
  if (stderr.includes("claude-mcp-browser-bridge") && (stderr.includes("EOPNOTSUPP") || stderr.includes("ENOENT"))) {
16092
16099
  return "Claude browser bridge state from a previous session is no longer accessible. This typically happens when a session with Chrome integration is resumed after a restart.";
16093
16100
  }
16101
+ if (stderr.includes("No conversation found with session ID")) {
16102
+ return "The conversation history for this session no longer exists. This can happen if Claude's history was cleared or if the session was created on a different machine.";
16103
+ }
16094
16104
  return null;
16095
16105
  }
16096
16106
  kill() {
16097
16107
  this.stopStatusWatch();
16098
- this.process?.kill("SIGTERM");
16108
+ if (!this.process)
16109
+ return;
16110
+ const proc = this.process;
16099
16111
  this.process = null;
16112
+ proc.kill("SIGINT");
16113
+ const secondSigint = setTimeout(() => {
16114
+ try {
16115
+ proc.kill("SIGINT");
16116
+ } catch {}
16117
+ }, 100);
16118
+ const forceKillTimeout = setTimeout(() => {
16119
+ try {
16120
+ proc.kill("SIGTERM");
16121
+ } catch {}
16122
+ }, 2000);
16123
+ proc.once("exit", () => {
16124
+ clearTimeout(secondSigint);
16125
+ clearTimeout(forceKillTimeout);
16126
+ });
16100
16127
  }
16101
16128
  interrupt() {
16102
16129
  if (!this.process)
@@ -20578,6 +20605,7 @@ async function startSession(options, username, displayName, replyToPostId, platf
20578
20605
  isResumed: false,
20579
20606
  resumeFailCount: 0,
20580
20607
  wasInterrupted: false,
20608
+ hasClaudeResponded: false,
20581
20609
  inProgressTaskStart: null,
20582
20610
  activeToolStarts: new Map,
20583
20611
  firstPrompt: options.prompt,
@@ -20621,7 +20649,6 @@ async function startSession(options, username, displayName, replyToPostId, platf
20621
20649
  }
20622
20650
  session.messageCount++;
20623
20651
  claude.sendMessage(content);
20624
- ctx.ops.persistSession(session);
20625
20652
  }
20626
20653
  async function resumeSession(state, ctx) {
20627
20654
  const shortId = state.threadId.substring(0, 8);
@@ -20700,6 +20727,7 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
20700
20727
  isResumed: true,
20701
20728
  resumeFailCount: state.resumeFailCount || 0,
20702
20729
  wasInterrupted: false,
20730
+ hasClaudeResponded: true,
20703
20731
  inProgressTaskStart: null,
20704
20732
  activeToolStarts: new Map,
20705
20733
  worktreeInfo: state.worktreeInfo,
@@ -20808,15 +20836,30 @@ async function handleExit(sessionId, code, ctx) {
20808
20836
  log10.debug(`Session ${shortId}... exited after interrupt, preserving for resume`);
20809
20837
  ctx.ops.stopTyping(session);
20810
20838
  cleanupSessionTimers(session);
20811
- ctx.ops.persistSession(session);
20839
+ if (session.hasClaudeResponded) {
20840
+ ctx.ops.persistSession(session);
20841
+ }
20812
20842
  mutableSessions(ctx).delete(session.sessionId);
20813
20843
  cleanupPostIndex(ctx, session.threadId);
20814
20844
  keepAlive.sessionEnded();
20815
- await withErrorHandling(() => postInfo(session, `\u2139\uFE0F Session paused. Send a new message to continue.`), { action: "Post session pause notification", session });
20845
+ const message = session.hasClaudeResponded ? `\u2139\uFE0F Session paused. Send a new message to continue.` : `\u2139\uFE0F Session ended before Claude could respond. Send a new message to start fresh.`;
20846
+ await withErrorHandling(() => postInfo(session, message), { action: "Post session pause notification", session });
20816
20847
  log10.info(`Session paused (${shortId}\u2026) \u2014 ${ctx.state.sessions.size} active`);
20817
20848
  await ctx.ops.updateStickyMessage();
20818
20849
  return;
20819
20850
  }
20851
+ if (!session.hasClaudeResponded && !session.isResumed) {
20852
+ log10.debug(`Session ${shortId}... exited before Claude responded, not persisting`);
20853
+ ctx.ops.stopTyping(session);
20854
+ cleanupSessionTimers(session);
20855
+ mutableSessions(ctx).delete(session.sessionId);
20856
+ cleanupPostIndex(ctx, session.threadId);
20857
+ keepAlive.sessionEnded();
20858
+ await withErrorHandling(() => postWarning(session, `**Session ended** before Claude could respond (exit code ${code}). Please start a new session.`), { action: "Post early exit notification", session });
20859
+ log10.info(`Session ended early (${shortId}\u2026) \u2014 ${ctx.state.sessions.size} active`);
20860
+ await ctx.ops.updateStickyMessage();
20861
+ return;
20862
+ }
20820
20863
  if (session.isResumed && code !== 0) {
20821
20864
  const MAX_RESUME_FAILURES = 3;
20822
20865
  session.resumeFailCount = (session.resumeFailCount || 0) + 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "0.33.1",
3
+ "version": "0.33.3",
4
4
  "description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",