claude-threads 0.33.7 → 0.33.9

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,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.33.8] - 2026-01-04
11
+
12
+ ### Fixed
13
+ - **Session resume broken after v0.33.7** - Fixed migration issue where sessions persisted with the old `timeoutPostId` field name couldn't be resumed after upgrading to v0.33.7. The `timeoutPostId` → `lifecyclePostId` rename now includes a proper migration that converts existing sessions on first load.
14
+ - **Defensive defaults for persisted session fields** - Session resume now uses safe defaults for all optional fields, preventing crashes when loading sessions from older versions that may have missing fields. Fields like `sessionAllowedUsers`, `planApproved`, `forceInteractivePermissions`, etc. now gracefully default instead of potentially causing undefined errors.
15
+ - **Validate required fields before resume** - Sessions with missing critical fields (`threadId`, `platformId`, `claudeSessionId`, `workingDir`) are now skipped gracefully with a warning instead of crashing.
16
+
10
17
  ## [0.33.7] - 2026-01-04
11
18
 
12
19
  ### Changed
package/dist/index.js CHANGED
@@ -13792,6 +13792,20 @@ class SessionStore {
13792
13792
  log3.warn(`Sessions file version ${data.version} not supported, starting fresh`);
13793
13793
  return sessions;
13794
13794
  }
13795
+ let needsSave = false;
13796
+ for (const session of Object.values(data.sessions)) {
13797
+ const legacySession = session;
13798
+ if (legacySession.timeoutPostId && !session.lifecyclePostId) {
13799
+ session.lifecyclePostId = legacySession.timeoutPostId;
13800
+ delete legacySession.timeoutPostId;
13801
+ needsSave = true;
13802
+ log3.debug(`Migrated timeoutPostId to lifecyclePostId for session ${session.threadId.substring(0, 8)}...`);
13803
+ }
13804
+ }
13805
+ if (needsSave) {
13806
+ log3.info("Migrated session(s) from timeoutPostId to lifecyclePostId");
13807
+ this.writeAtomic(data);
13808
+ }
13795
13809
  for (const session of Object.values(data.sessions)) {
13796
13810
  if (session.cleanedAt)
13797
13811
  continue;
@@ -13877,7 +13891,9 @@ class SessionStore {
13877
13891
  historySessions.push(session);
13878
13892
  continue;
13879
13893
  }
13880
- if (session.lifecyclePostId && activeSessions && !activeSessions.has(sessionId)) {
13894
+ const legacySession = session;
13895
+ const hasLifecyclePost = session.lifecyclePostId || legacySession.timeoutPostId;
13896
+ if (hasLifecyclePost && activeSessions && !activeSessions.has(sessionId)) {
13881
13897
  historySessions.push(session);
13882
13898
  }
13883
13899
  }
@@ -13923,7 +13939,12 @@ class SessionStore {
13923
13939
  for (const session of Object.values(data.sessions)) {
13924
13940
  if (session.platformId !== platformId)
13925
13941
  continue;
13926
- if (session.lifecyclePostId === postId || session.sessionStartPostId === postId) {
13942
+ const legacySession = session;
13943
+ const lifecycleId = session.lifecyclePostId || legacySession.timeoutPostId;
13944
+ if (lifecycleId === postId || session.sessionStartPostId === postId) {
13945
+ if (legacySession.timeoutPostId && !session.lifecyclePostId) {
13946
+ session.lifecyclePostId = legacySession.timeoutPostId;
13947
+ }
13927
13948
  return session;
13928
13949
  }
13929
13950
  }
@@ -20687,6 +20708,16 @@ async function startSession(options, username, displayName, replyToPostId, platf
20687
20708
  claude.sendMessage(content);
20688
20709
  }
20689
20710
  async function resumeSession(state, ctx) {
20711
+ if (!state.threadId || !state.platformId || !state.claudeSessionId || !state.workingDir) {
20712
+ const missing = [
20713
+ !state.threadId && "threadId",
20714
+ !state.platformId && "platformId",
20715
+ !state.claudeSessionId && "claudeSessionId",
20716
+ !state.workingDir && "workingDir"
20717
+ ].filter(Boolean).join(", ");
20718
+ log10.warn(`Skipping session with missing required fields: ${missing}`);
20719
+ return;
20720
+ }
20690
20721
  const shortId = state.threadId.substring(0, 8);
20691
20722
  const platforms = ctx.state.platforms;
20692
20723
  const platform = platforms.get(state.platformId);
@@ -20735,11 +20766,11 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
20735
20766
  sessionId,
20736
20767
  platform,
20737
20768
  claudeSessionId: state.claudeSessionId,
20738
- startedBy: state.startedBy,
20769
+ startedBy: state.startedBy || "unknown",
20739
20770
  startedByDisplayName: state.startedByDisplayName,
20740
- startedAt: new Date(state.startedAt),
20771
+ startedAt: state.startedAt ? new Date(state.startedAt) : new Date,
20741
20772
  lastActivityAt: new Date,
20742
- sessionNumber: state.sessionNumber,
20773
+ sessionNumber: state.sessionNumber ?? 1,
20743
20774
  workingDir: state.workingDir,
20744
20775
  claude,
20745
20776
  currentPostId: null,
@@ -20747,11 +20778,11 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
20747
20778
  pendingApproval: null,
20748
20779
  pendingQuestionSet: null,
20749
20780
  pendingMessageApproval: null,
20750
- planApproved: state.planApproved,
20751
- sessionAllowedUsers: new Set(state.sessionAllowedUsers),
20752
- forceInteractivePermissions: state.forceInteractivePermissions,
20753
- sessionStartPostId: state.sessionStartPostId,
20754
- tasksPostId: state.tasksPostId,
20781
+ planApproved: state.planApproved ?? false,
20782
+ sessionAllowedUsers: new Set(state.sessionAllowedUsers || [state.startedBy].filter(Boolean)),
20783
+ forceInteractivePermissions: state.forceInteractivePermissions ?? false,
20784
+ sessionStartPostId: state.sessionStartPostId ?? null,
20785
+ tasksPostId: state.tasksPostId ?? null,
20755
20786
  lastTasksContent: state.lastTasksContent ?? null,
20756
20787
  tasksCompleted: state.tasksCompleted ?? false,
20757
20788
  tasksMinimized: state.tasksMinimized ?? false,
@@ -20761,7 +20792,7 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
20761
20792
  timeoutWarningPosted: false,
20762
20793
  isRestarting: false,
20763
20794
  isResumed: true,
20764
- resumeFailCount: state.resumeFailCount || 0,
20795
+ resumeFailCount: state.resumeFailCount ?? 0,
20765
20796
  wasInterrupted: false,
20766
20797
  hasClaudeResponded: true,
20767
20798
  inProgressTaskStart: null,
@@ -20776,6 +20807,7 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
20776
20807
  sessionDescription: state.sessionDescription,
20777
20808
  pullRequestUrl: state.pullRequestUrl,
20778
20809
  messageCount: state.messageCount ?? 0,
20810
+ lifecyclePostId: state.lifecyclePostId,
20779
20811
  statusBarTimer: null
20780
20812
  };
20781
20813
  mutableSessions(ctx).set(sessionId, session);
@@ -20791,8 +20823,8 @@ Please start a new session.`, state.threadId), { action: "Post resume failure no
20791
20823
  try {
20792
20824
  claude.start();
20793
20825
  log10.info(`Resumed session ${shortId}... (@${state.startedBy})`);
20794
- if (state.lifecyclePostId) {
20795
- await withErrorHandling(() => session.platform.updatePost(state.lifecyclePostId, `\uD83D\uDD04 **Session resumed** by @${state.startedBy}
20826
+ if (session.lifecyclePostId) {
20827
+ await withErrorHandling(() => session.platform.updatePost(session.lifecyclePostId, `\uD83D\uDD04 **Session resumed** by @${session.startedBy}
20796
20828
  *Reconnected to Claude session. You can continue where you left off.*`), { action: "Update timeout/shutdown post for resume", session });
20797
20829
  session.lifecyclePostId = undefined;
20798
20830
  } else {
@@ -21581,8 +21613,9 @@ function formatHistoryEntry(session) {
21581
21613
  const topic = getHistorySessionTopic(session);
21582
21614
  const threadLink = `[${topic}](/_redirect/pl/${session.threadId})`;
21583
21615
  const displayName = session.startedByDisplayName || session.startedBy;
21584
- const isTimedOut = !session.cleanedAt && session.lifecyclePostId;
21585
- const lastActivity = new Date(session.lastActivityAt);
21616
+ const legacySession = session;
21617
+ const isTimedOut = !session.cleanedAt && (session.lifecyclePostId || legacySession.timeoutPostId);
21618
+ const lastActivity = session.lastActivityAt ? new Date(session.lastActivityAt) : new Date;
21586
21619
  const time = formatRelativeTimeShort(lastActivity);
21587
21620
  const prStr = session.pullRequestUrl ? ` \xB7 ${formatPullRequestLink(session.pullRequestUrl)}` : "";
21588
21621
  const indicator = isTimedOut ? "\u23F8\uFE0F" : "\u2713";
@@ -21969,7 +22002,7 @@ class SessionManager {
21969
22002
  }
21970
22003
  return false;
21971
22004
  }
21972
- const allowedUsers = new Set(persistedSession.sessionAllowedUsers);
22005
+ const allowedUsers = new Set(persistedSession.sessionAllowedUsers || []);
21973
22006
  const platform = this.platforms.get(platformId);
21974
22007
  if (!allowedUsers.has(username) && !platform?.isUserAllowed(username)) {
21975
22008
  if (platform) {
@@ -22380,7 +22413,7 @@ class SessionManager {
22380
22413
  if (!session) {
22381
22414
  const persisted = this.getPersistedSession(threadId);
22382
22415
  if (persisted) {
22383
- return persisted.sessionAllowedUsers.includes(username) || this.platforms.get(persisted.platformId)?.isUserAllowed(username) || false;
22416
+ return (persisted.sessionAllowedUsers || []).includes(username) || this.platforms.get(persisted.platformId)?.isUserAllowed(username) || false;
22384
22417
  }
22385
22418
  return false;
22386
22419
  }
@@ -22672,7 +22705,7 @@ Release notes not available. See [GitHub releases](https://github.com/anneschuth
22672
22705
  const content = mattermost.isBotMentioned(message) ? mattermost.extractPrompt(message) : message.trim();
22673
22706
  const persistedSession = session.getPersistedSession(threadRoot);
22674
22707
  if (persistedSession) {
22675
- const allowedUsers = new Set(persistedSession.sessionAllowedUsers);
22708
+ const allowedUsers = new Set(persistedSession.sessionAllowedUsers || []);
22676
22709
  if (!allowedUsers.has(username) && !mattermost.isUserAllowed(username)) {
22677
22710
  await mattermost.createPost(`\u26A0\uFE0F @${username} is not authorized to resume this session`, threadRoot);
22678
22711
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "0.33.7",
3
+ "version": "0.33.9",
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",