claude-threads 1.36.0 → 1.37.0

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,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.37.0] - 2026-09-11
11
+
12
+ ### Added
13
+ - **Per-platform `lifecycle` visibility** (#505, thanks @kaza). Session status posts — the idle warning, the timeout notice, the pause notice, the resume notice and the shutdown notice a deploy leaves in every open thread — are the bulk of what a quiet channel contains when the bot is used as an assistant rather than a task runner. `lifecycle: minimal` drops the idle warning, which predicts a timeout the next message would undo anyway; `hidden` drops the status posts entirely. Defaults to `full`, so nothing changes unless you set it. Editing a post the thread already has is never suppressed: it adds no post and no notification, and leaving a stale "session idle" up across a restart would read worse than the edit. An abnormal exit survives every level, because a session that died must not look like one that finished. At `hidden` there is no post for a 🔄 reaction to resume from, so the channel sticky tells the reader to send a message instead.
14
+ - **Per-platform `turnMarker`: the daemon says when a turn is over** (#528, thanks @kaza). An end-of-turn signal an integration can wait for, instead of guessing from "it has been quiet for eight seconds". On Slack it rides as invisible message metadata on the turn's last reply; on Mattermost, which has no metadata channel, as a 🏁 reaction. Default `off`, so nothing appears unless you ask for it. The payload carries a version (`v`), and `docs/CONFIGURATION.md` states what is frozen: `event_type` does not change within a 1.x line and `event_payload` only ever gains fields, `session` is stable across restarts and respawns, and `turn` restarts at 1 after a bot restart, so it is an ordering hint within one run rather than a unique key. Each marked turn costs one extra API call.
15
+ - **`CLAUDE_THREADS_HOME` moves the state root** (#557, thanks @Jadefalkner). Everything the bot persists — config, sessions, thread logs, worktree metadata, memory, uploads — resolves from one root instead of `homedir()` directly, so a second bot can run as the same user by pointing at a different directory. Unset, every path resolves exactly as before: this is a refactor at the default, and an existing install upgrades with its sessions intact.
16
+
17
+ ### Changed
18
+ - **⚠️ Two bots sharing one `$HOME` will no longer both start** (#557). A one-process-per-root lock now refuses the second, naming the pid that holds it and the fix. This is a deliberate break of a setup that appeared to work: the two instances were writing the same `sessions.json` and quietly losing each other's sessions. If you run more than one bot as the same user, give each its own `CLAUDE_THREADS_HOME` before upgrading. A lock left behind by a killed process is taken over automatically.
19
+
20
+ ### Fixed
21
+ - **Verifying the `bugReports` gate no longer files a public issue** (#586). `createGitHubIssue` called `execSync` directly, so the one test that proves the gate is load-bearing reached the real `gh` CLI the moment the gate was removed. Anyone following the repo's own red-green rule therefore filed a public issue titled "T" from their test suite, three times in one afternoon (#581, #582, #583). The command is now injectable, the same way `checkGitHubCli` already was, and the test passes a stub and asserts directly that no subprocess runs. A second case covers the other direction, so a stub that was never wired up cannot make the first one pass for the wrong reason.
22
+
23
+ ## [1.36.1] - 2026-09-10
24
+
25
+ ### Fixed
26
+ - **An answer given before the option emojis finish landing is no longer lost.** A question or plan-approval post goes up first, then its 1️⃣/2️⃣ (or 👍/👎) options are added one API round trip at a time. The post is already visible and reactable for that whole window, but the bot only claimed it afterwards: `MessageManager` registered the post id in the session's reaction index after `createInteractivePost` resolved, and `QuestionApprovalExecutor` recorded `currentPostId` / `pendingApproval` at the same late moment. A reaction arriving in between matched nothing and was discarded silently, with no retry and no fallback. On a modern CLI, where `AskUserQuestion` and `ExitPlanMode` block on the MCP permission prompt, the cost is not a missed click: the decision bridge stays parked and the session hangs until `MCP_TOOL_TIMEOUT` (an hour by default) on a decision the user already made. Both the post id and the executor's pending state are now claimed in a pre-reaction callback, before the first option emoji is sent. The same ordering is fixed for every reaction-gated prompt that goes through `postInteractiveAndRegister` (worktree prompts, message approvals, update prompts, bug reports, routine and watch confirmations). Whoever reacts fastest was most likely to lose their answer, and a loaded host widened the window enough to make it reproducible.
27
+ - **A reaction that beats the bot's own create response survives too.** Claiming the post id before the option emojis go out closes only the second half of the race. The platform starts accepting reactions the instant it stores the post, which is earlier still than the `POST /posts` response getting back to the bot, so on a slow or contended link a user can answer before the bot has any idea the post exists. The reaction arrives as a live websocket push and is never re-delivered, so `findByPost` dropped it for good and the session hung on the decision bridge exactly as before. A thread now announces an interactive-post create before issuing it, and a reaction on an unknown post id waits (up to 5s) for that registration instead of being discarded. The wait applies only while a create is genuinely outstanding: a reaction on any unrelated post still returns immediately, so nothing else pays for it.
28
+
10
29
  ## [1.36.0] - 2026-09-10
11
30
 
12
31
  ### Added
@@ -7,6 +7,7 @@
7
7
  #
8
8
  # For other exit codes:
9
9
  # - 0: Clean exit, don't restart
10
+ # - 3: Refused to start (another instance holds the state directory), don't restart
10
11
  # - 1+: Error, optional restart based on flags
11
12
  #
12
13
  # PLATFORM SUPPORT:
@@ -32,6 +33,9 @@ set -e
32
33
 
33
34
  # Exit code that signals "restart for update"
34
35
  RESTART_EXIT_CODE=42
36
+ # Exit code that signals "refused to start" (another instance holds the state
37
+ # directory): never restart, a retry would only collide again.
38
+ LOCKED_EXIT_CODE=3
35
39
 
36
40
  # Configuration
37
41
  MAX_RESTARTS=${CLAUDE_THREADS_MAX_RESTARTS:-0} # 0 = unlimited
@@ -92,6 +96,7 @@ while [[ $# -gt 0 ]]; do
92
96
  echo ""
93
97
  echo "Exit codes:"
94
98
  echo " 0 Clean exit, no restart"
99
+ echo " 3 Refused to start (another instance holds the state directory), no restart"
95
100
  echo " 42 Update restart signal (always restarts)"
96
101
  echo " other Error (restarts only with --restart-on-error)"
97
102
  exit 0
@@ -162,6 +167,9 @@ while true; do
162
167
  # Clean exit
163
168
  log "Clean exit, not restarting"
164
169
  should_restart=false
170
+ elif [ $exit_code -eq $LOCKED_EXIT_CODE ]; then
171
+ log "Another instance holds the state directory, not restarting"
172
+ should_restart=false
165
173
  elif [ "$RESTART_ON_ERROR" = true ]; then
166
174
  # Error exit with restart-on-error enabled
167
175
  log "Error exit, restarting due to --restart-on-error"