claude-slack-channel-bots 0.16.1 → 0.17.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/README.md +3 -1
- package/docs/bots.md +13 -9
- package/docs/channel-modes.md +1 -1
- package/docs/configuration.md +5 -15
- package/docs/migration.md +17 -12
- package/docs/reload.md +5 -3
- package/docs/scheduled-prompts.md +6 -8
- package/docs/server-settings.md +17 -9
- package/docs/troubleshooting.md +9 -6
- package/package.json +2 -1
- package/skills/debug-slack-channel-bots/SKILL.md +268 -143
- package/src/ack-tracker.ts +68 -1
- package/src/ad-settings.ts +25 -5
- package/src/ad-version-gate.ts +35 -4
- package/src/agent-director-errors.ts +2 -0
- package/src/agent-director-startup.ts +34 -0
- package/src/agent-director-template.ts +8 -29
- package/src/bot-system-prompt.ts +53 -0
- package/src/codex-config-keys.ts +307 -0
- package/src/config.ts +84 -2
- package/src/cron-dispatch.ts +36 -198
- package/src/cron-log.ts +4 -2
- package/src/delivery-ad-scope.ts +15 -0
- package/src/delivery-constants.ts +13 -0
- package/src/delivery-decision.ts +6 -3
- package/src/delivery-engine.ts +514 -0
- package/src/delivery-intake.ts +146 -0
- package/src/delivery-lifecycle.ts +100 -0
- package/src/delivery-notices.ts +179 -0
- package/src/delivery-policy.ts +113 -0
- package/src/delivery-queue.ts +451 -0
- package/src/delivery-receipt.ts +195 -0
- package/src/delivery-screen.ts +169 -0
- package/src/health-check.ts +26 -47
- package/src/interject.ts +10 -88
- package/src/jsonl-persistence-check.ts +1 -0
- package/src/lib.ts +1 -15
- package/src/live-row-sequence.ts +28 -0
- package/src/lost-message.ts +1 -87
- package/src/outage-state.ts +23 -3
- package/src/pending-row.ts +6 -0
- package/src/permission-poller.ts +5 -0
- package/src/persona-diagnostics.ts +31 -0
- package/src/persona-episodes.ts +7 -4
- package/src/persona-identity.ts +8 -1
- package/src/persona-lifecycle.ts +1 -1
- package/src/persona-routing.ts +88 -525
- package/src/registry.ts +19 -35
- package/src/reload-plan.ts +7 -4
- package/src/reload-preview-clauses.ts +5 -0
- package/src/reload.ts +8 -0
- package/src/restart.ts +11 -19
- package/src/server.ts +241 -60
- package/src/session-manager.ts +378 -74
- package/src/slow-recovery.ts +6 -6
- package/src/tag-description.ts +10 -0
- package/src/tag-wrapper.ts +100 -0
- package/src/test-mcp-drop.ts +32 -0
- package/src/test-timer-overrides.ts +134 -0
- package/src/unavailable-retry.ts +16 -15
- package/stop-hooks/slack-reply-guard.sh +42 -8
- package/src/inbound-dedupe.ts +0 -105
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ A single HTTP MCP server that runs several independent Claude Code bots, called
|
|
|
47
47
|
## Prerequisites
|
|
48
48
|
|
|
49
49
|
- [Bun](https://bun.sh) `>= 1.0.21` (agent-director minimum)
|
|
50
|
-
- [Claude Code](https://claude.ai/code) 2.1.280 or later for the workers, installed and authenticated: CSCB's supported minimum and the version the fleet runs. A Claude Code too old for exec-form hooks (older than 2.1.139) has its hooks ignored, so every launch stays `pending`. The `claude` on the workers' `PATH` must be Claude Code's native binary or a wrapper that `exec`s it: a launcher or shim that runs Claude Code as a child process makes agent-director ignore every hook (`ad.hook.ignored` with the reason `pid_mismatch`), so every launch stays `pending`.
|
|
50
|
+
- [Claude Code](https://claude.ai/code) 2.1.280 or later for the workers, installed and authenticated: CSCB's supported minimum and the version the fleet runs. No version pin or channels permission is required. A Claude Code too old for exec-form hooks (older than 2.1.139) has its hooks ignored, so every launch stays `pending`. The `claude` on the workers' `PATH` must be Claude Code's native binary or a wrapper that `exec`s it: a launcher or shim that runs Claude Code as a child process makes agent-director ignore every hook (`ad.hook.ignored` with the reason `pid_mismatch`), so every launch stays `pending`.
|
|
51
51
|
- [`agent-director`](https://github.com/gabemahoney/agent-director) 0.12.0 or later, **installed system-wide** as a prerequisite, like `git` or `docker`. The npm `agent-director` package CSCB depends on is the client only: it finds the system-installed binary at startup and refuses to start when the binary is missing, too old, or unreachable. This release pins the client to `agent-director@0.12.0` and requires the CLI at 0.12.0 or later, installed on the host together with it, and the two are rolled back together; CSCB changes no agent-director code (see "Switching over to agent-director Phase 1" in [Migration](docs/migration.md#migration)). Every agent on the host, with every long-running agent-director process (`agent-director serve` included), is stopped before either binary change (the Phase 1 install, and the rollback's restore of the previous binary) and started again after it. The startup gate refuses a binary below the client's own minimum (`min_binary_version` in the client's `dist/version-floor.json`) or below 0.12.0, and names the required version (see [Startup errors](docs/troubleshooting.md#startup-errors)). To check the install before you start, run `bun run install-check` (see [Checking your agent-director install](docs/agent-director.md#checking-your-agent-director-install)).
|
|
52
52
|
- [tmux](https://github.com/tmux/tmux) 3.2 or later, with `remain-on-exit` off. agent-director runs it; CSCB starts no tmux process. agent-director does not depend on tmux's `base-index` or `pane-base-index`, so their values don't matter.
|
|
53
53
|
- Slack workspace admin access (to create and configure one Slack app per persona)
|
|
@@ -111,6 +111,8 @@ Replace the channel ID and paths with your own. [Configuration](docs/configurati
|
|
|
111
111
|
|
|
112
112
|
Talk to a persona in its channels or, when its `dm.enabled` is `true`, by direct message. When its Claude instance needs a tool approved, an **Allow** / **Deny** prompt is posted to its destination, its `permission_prompts` (see [Permission Relay](docs/bots.md#permission-relay)).
|
|
113
113
|
|
|
114
|
+
CSCB durably queues accepted messages and types them into the persona’s terminal. Delivery is ordered and at-least-once: the next item waits for transcript confirmation of the previous one, and an unconfirmed item may appear again. The bot answers with `reply`; see [How a persona receives messages](docs/bots.md#how-a-persona-receives-messages).
|
|
115
|
+
|
|
114
116
|
### Changing the configuration
|
|
115
117
|
|
|
116
118
|
On a running server, saving `config.json` changes nothing yet. Within about 5 seconds the server writes a preview of the change to `config.json.pending`. Read it, then confirm it by renaming it:
|
package/docs/bots.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# What a bot can do
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tools, receiving messages, injecting a message, permission prompts and the Slack Reply Guard. See also [Scheduled Prompts](scheduled-prompts.md).
|
|
4
4
|
|
|
5
5
|
## Tools
|
|
6
6
|
|
|
@@ -32,9 +32,9 @@ On a host whose Claude Code permission settings don't allow the server's tools a
|
|
|
32
32
|
|
|
33
33
|
## Interject
|
|
34
34
|
|
|
35
|
-
POST to `/interject` to
|
|
35
|
+
POST to `/interject` to store a message for a configured persona from localhost. The message is queued only for the named persona, including while it is down. Each request is an independent queued item, even with identical sender and text; requests are never merged or deduplicated. Only requests from `127.0.0.1` or `::1` are accepted — external callers are rejected with 403. The bot sees it as described in [How a persona receives messages](#how-a-persona-receives-messages); the Slack Reply Guard does not remind it to reply.
|
|
36
36
|
|
|
37
|
-
An injected message carries no Slack conversation
|
|
37
|
+
An injected message carries no Slack conversation; say in its text where to post any Slack reply.
|
|
38
38
|
|
|
39
39
|
### Request
|
|
40
40
|
|
|
@@ -52,7 +52,7 @@ curl -X POST http://localhost:<port>/interject \
|
|
|
52
52
|
|
|
53
53
|
### Response
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
After durable storage, returns HTTP 200. This acknowledges storage, not delivery:
|
|
56
56
|
|
|
57
57
|
```json
|
|
58
58
|
{ "ok": true, "persona": "planner" }
|
|
@@ -69,7 +69,7 @@ On success, returns HTTP 200:
|
|
|
69
69
|
| 404 | No persona in the applied configuration has that name or key. |
|
|
70
70
|
| 405 | Must use POST method. |
|
|
71
71
|
| 413 | Request body exceeds 32KB. |
|
|
72
|
-
|
|
|
72
|
+
| 500 | The message could not be stored (`Message not stored`). |
|
|
73
73
|
|
|
74
74
|
### Example: crontab reminder
|
|
75
75
|
|
|
@@ -116,13 +116,15 @@ The template also pre-allows each bot to read its own persistent-memory director
|
|
|
116
116
|
|
|
117
117
|
## How a persona receives messages
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
CSCB stores accepted messages, then types their text into the bot’s terminal in a `<channel source="slack-channel-router" …>` tag. Every Slack message carries its sender’s ID; use the shared prompt file to say whose instructions to follow. The bot answers through `reply`. A message from Slack carries a `via` attribute that says how it reached the persona. An injected message (a scheduled prompt or an `/interject` message) carries no `via`. A received item is durably stored; delivered means its complete text is confirmed in the bot’s transcript. Delivery is at-least-once: an unconfirmed item is offered again, so the bot may see it more than once. Each persona uses strict FIFO across Slack, `/interject` and scheduled prompts, with one item in flight; the next waits until the previous item is confirmed delivered. There is no age cutoff. Messages wait while MCP is disconnected, trigger the configured reconnect, and are delivered once connected again. Which channels a persona serves, and at which delivery, depends on the channel mode (see [Channel modes](channel-modes.md#channel-modes)).
|
|
120
120
|
|
|
121
121
|
A message from Slack carries these tag attributes:
|
|
122
122
|
|
|
123
123
|
| Attribute | Value |
|
|
124
124
|
|---|---|
|
|
125
125
|
| `chat_id` | The channel ID, or the DM conversation ID (`D…`) for a direct message. |
|
|
126
|
+
| `channel_name` | Channel name without `#`; `dm` for a DM; channel ID if the name cannot be resolved. |
|
|
127
|
+
| `part` | `k/n` for part k of n when a long message is split. |
|
|
126
128
|
| `message_id`, `ts` | The message's Slack timestamp, for example `1789936743.069939`. |
|
|
127
129
|
| `user` | The author's Slack display name, falling back to real name, then Slack username, then user ID. For a post by a bot or integration without a user: the post's username, else its bot profile name, else its bot ID. |
|
|
128
130
|
| `user_id` | The author's Slack user ID. |
|
|
@@ -140,7 +142,7 @@ Each way a message reaches a persona:
|
|
|
140
142
|
| `@here` / `@channel` broadcast | A message that uses `@here` or `@channel`. In declarative mode: in a channel listed in the persona's `channels`, with either `delivery`. In fungible mode: in a public or private channel its app is in that is not externally shared, at either channel delivery. `@everyone` and user-group mentions are not broadcasts. | Slack attributes | `broadcast` | Yes, broadcast wording |
|
|
141
143
|
| Every message, shared channel | A message that neither @mentions the persona nor broadcasts. In declarative mode: in a channel listed in its `channels` with `delivery: all`, when at least one other persona in the applied configuration also has `delivery: all` for that channel, whether or not that persona is up. In fungible mode: in a public or private channel its app is in that is not externally shared, where its channel delivery is `all` (a stored choice, after the loop guard; see [Channel delivery in fungible mode](channel-modes.md#channel-delivery-in-fungible-mode)), when at least one other persona in the applied configuration has a channel delivery of `all` for that channel by the same rule, whether or not that persona's app remains in the channel. | Slack attributes | `receive_all_shared` | Yes, shared-channel wording |
|
|
142
144
|
| Every message, this persona alone | A message that neither @mentions the persona nor broadcasts. In declarative mode: in a channel listed in its `channels` with `delivery: all`, when no other persona in the applied configuration has `delivery: all` for that channel. In fungible mode: in a public or private channel its app is in that is not externally shared, where its channel delivery is `all`, when no other persona in the applied configuration has a channel delivery of `all` for that channel. | Slack attributes | `receive_all` | Yes, only-you wording |
|
|
143
|
-
| Scheduled prompt | A crontable line naming the persona fires, and the scheduler
|
|
145
|
+
| Scheduled prompt | A crontable line naming the persona fires, and the scheduler queues it for that persona only (see [Scheduled Prompts](scheduled-prompts.md#scheduled-prompts-cscb_cron)). | Only `user` = `cscb-cron:<prompt file name without its extension>` (`cscb-cron:standup` for `standup.md`) and `ts` = the fire time in seconds, with six decimals. No `chat_id`, `message_id` or `via`. | none | Never |
|
|
144
146
|
| `/interject` message | A localhost POST to `/interject` addressed by `persona` (see [Interject](#interject)); it reaches only that persona. | Only `user` = the request's `sender` (default `interject`) and `ts`, as for a scheduled prompt. No `chat_id`, `message_id` or `via`. | none | Never |
|
|
145
147
|
|
|
146
148
|
A "Yes" in the Reminder column means the [Slack Reply Guard](#slack-reply-guard-stop-hook) reminds the persona once if it ends its turn without replying, wherever the reminder is on for that persona. Each `via` has its own reminder wording, stating how the message arrived; that section lists them.
|
|
@@ -150,7 +152,7 @@ A "Yes" in the Reminder column means the [Slack Reply Guard](#slack-reply-guard-
|
|
|
150
152
|
- **Every author counts.** Posts by people, bots, integrations and other personas all arrive by these rules. Each persona posts as its own Slack app, so another persona's post reaches it like anyone else's.
|
|
151
153
|
- **Anyone in the workspace can reach a persona,** in both modes: in declarative mode in its listed channels, by each channel's `delivery`; in fungible mode in every channel its app is invited to that it serves, by its channel delivery there; and in both modes by DM when its `dm.enabled` is `true`. There is no approval step. In fungible mode an invite changes who that is (see [Who can reach a persona in fungible mode](channel-modes.md#who-can-reach-a-persona-in-fungible-mode)).
|
|
152
154
|
- **A persona never receives its own posts.**
|
|
153
|
-
- **
|
|
155
|
+
- **Slack redeliveries add no second queue item, including across server restarts.** Each accepted Slack message keeps its key for at least 24 hours after intake; the paired channel @mention adds no second item.
|
|
154
156
|
- **In fungible mode, an @mention is delivered from its `message` event.** Of the two events Slack sends for a channel @mention, the `app_mention` event decides nothing and the `message` event carrying the same mention decides, so each @mention is delivered once, whichever event arrives first. An edit that adds a mention to an earlier message sends no new `message` event, so in fungible mode it is not delivered.
|
|
155
157
|
- **A message without `via` is an injected prompt.** Its `user` label is free-form, so an `/interject` sender label, even one starting with `cscb-cron:`, cannot make it look like a Slack message.
|
|
156
158
|
- **When the reminder is off.** A persona gets no reminder when its effective `stop_hook_bootstrap` was `false` at its last launch, when it has no `claude_config_dir` (its own or the top-level one), when that directory resolves to `~/.claude`, or when `jq` is not installed. See [Slack Reply Guard](#slack-reply-guard-stop-hook).
|
|
@@ -176,6 +178,8 @@ This turn started from a Slack direct message and you haven't replied. If you me
|
|
|
176
178
|
|
|
177
179
|
When several Slack messages started the turn, the last one picks the wording. A `reply` call after the earliest of them counts as a reply. The persona then continues once: it can reply, or end the turn without replying. That continuation carries `stop_hook_active=true`, which the guard always lets through, so the reminder appears at most once per turn and never loops.
|
|
178
180
|
|
|
181
|
+
CSCB types the tag, including when a message arrives mid-turn or is pasted into the composer. The guard recognises these forms in the transcript.
|
|
182
|
+
|
|
179
183
|
**Only messages from Slack trigger it.** The guard recognises a message from Slack by its `via` attribute; any `via` value counts. Injected prompts (scheduled prompts and `/interject` messages) carry no `via`, so they never trigger the reminder: no Slack conversation is waiting on them. An injected message that arrives right after a Slack message does not cancel the reminder for the Slack message. [How a persona receives messages](#how-a-persona-receives-messages) lists every kind of message and its `via`.
|
|
180
184
|
|
|
181
185
|
### Turning it on or off
|
|
@@ -190,4 +194,4 @@ The guard only recognises a reply made with `mcp__slack-channel-router__reply`.
|
|
|
190
194
|
|
|
191
195
|
### When it stays silent
|
|
192
196
|
|
|
193
|
-
The guard is fail-open: when anything is missing or goes wrong, the turn ends normally with no reminder. Besides the opt-out, it stays silent for a persona with no `claude_config_dir`, one whose directory resolves to your own `~/.claude` (the server never edits `~/.claude/settings.json`), any persona while `jq` is not installed (installing it later is enough), and when the message's `<channel>` tag, which
|
|
197
|
+
The guard is fail-open: when anything is missing or goes wrong, the turn ends normally with no reminder. Besides the opt-out, it stays silent for a persona with no `claude_config_dir`, one whose directory resolves to your own `~/.claude` (the server never edits `~/.claude/settings.json`), any persona while `jq` is not installed (installing it later is enough), and when the message's `<channel>` tag, which CSCB types, isn't in the shape the guard expects. After every CSCB or Claude Code upgrade, check the guard end to end with the steps in the debugging skill's ["Checking the guard after an upgrade"](../skills/debug-slack-channel-bots/SKILL.md#checking-the-guard-after-an-upgrade). How the server installs the hook and the per-persona record it reads are in ["How the guard is installed and what it reads"](../skills/debug-slack-channel-bots/SKILL.md#how-the-guard-is-installed-and-what-it-reads).
|
package/docs/channel-modes.md
CHANGED
|
@@ -68,7 +68,7 @@ In fungible mode, a persona's channel delivery for a channel sets which messages
|
|
|
68
68
|
|
|
69
69
|
## Permission prompts in fungible mode
|
|
70
70
|
|
|
71
|
-
In fungible mode, a persona's destination is its `invited.permission_prompts`, `"dm"` when it is absent. Its permission prompts and its server notices, such as
|
|
71
|
+
In fungible mode, a persona's destination is its `invited.permission_prompts`, `"dm"` when it is absent. Its permission prompts and its server notices, such as *Message not saved* notices and restart-limit warnings, are posted there as the persona. See the `invited.permission_prompts` row in [Persona fields](configuration.md#persona-fields).
|
|
72
72
|
|
|
73
73
|
- **A channel ID:** any channel; it needs no listing and no DMs. Invite the persona's app to it, or Slack refuses the posts (see "A permission prompt or notice doesn't arrive" in [Troubleshooting](troubleshooting.md#troubleshooting)). Everyone in that channel sees the prompts and notices.
|
|
74
74
|
- **`"dm"`, written or by default:** a DM from the persona's app to its `dm.contact`. It needs `dm.enabled: true` and a `dm.contact`.
|
package/docs/configuration.md
CHANGED
|
@@ -31,19 +31,14 @@ A complete `config.json` in declarative mode (`allow_invited_channels` absent),
|
|
|
31
31
|
"name": "planner",
|
|
32
32
|
"credentials_file": "~/.config/claude-slack-channel-bots/planner-credentials.json",
|
|
33
33
|
"working_directory": "~/projects/alpha",
|
|
34
|
-
"channels": [
|
|
35
|
-
{ "id": "C0123456789", "delivery": "all" },
|
|
36
|
-
{ "id": "C0555555555", "delivery": "all" }
|
|
37
|
-
],
|
|
34
|
+
"channels": [{ "id": "C0123456789", "delivery": "all" }, { "id": "C0555555555", "delivery": "all" }],
|
|
38
35
|
"permission_prompts": "C0123456789"
|
|
39
36
|
},
|
|
40
37
|
{
|
|
41
38
|
"name": "reviewer",
|
|
42
39
|
"credentials_file": "~/.config/claude-slack-channel-bots/reviewer-credentials.json",
|
|
43
40
|
"working_directory": "~/projects/beta",
|
|
44
|
-
"channels": [
|
|
45
|
-
{ "id": "C0555555555", "delivery": "mentions" }
|
|
46
|
-
],
|
|
41
|
+
"channels": [{ "id": "C0555555555", "delivery": "mentions" }],
|
|
47
42
|
"dm": { "enabled": true, "contact": "U0123456789" },
|
|
48
43
|
"permission_prompts": "dm",
|
|
49
44
|
"claude_config_dir": "~/.claude-reviewer",
|
|
@@ -61,8 +56,7 @@ A complete `config.json` in declarative mode (`allow_invited_channels` absent),
|
|
|
61
56
|
"session_restart_delay": 60,
|
|
62
57
|
"claude_config_dir": "~/.claude-bots",
|
|
63
58
|
"ack_reaction": "eyes",
|
|
64
|
-
"reply_chunk_limit": 3000,
|
|
65
|
-
"reply_chunk_mode": "newline"
|
|
59
|
+
"reply_chunk_limit": 3000, "reply_chunk_mode": "newline"
|
|
66
60
|
}
|
|
67
61
|
```
|
|
68
62
|
|
|
@@ -144,7 +138,6 @@ Each persona's Slack tokens live in its own credentials file, and `config.json`
|
|
|
144
138
|
Write the file with `claude-slack-channel-bots credentials <persona>` in your own terminal, once the persona is declared in `config.json`: the [credentials command](../skills/setup-slack-channel-bots/SKILL.md#credentials-command) of the setup wizard, `setup-slack-channel-bots`. It checks both tokens with Slack and writes the file, mode 0600, only when both pass; see [`claude-slack-channel-bots credentials`](cli.md#claude-slack-channel-bots-credentials).
|
|
145
139
|
|
|
146
140
|
To rotate a persona's tokens, run the credentials command for it again (see the wizard's [Rotate a persona's tokens](../skills/setup-slack-channel-bots/SKILL.md#rotate-a-personas-tokens)), then confirm the pending change (see [Confirming a change](reload.md#confirming-a-change)). Only that persona reconnects, and no restart is needed.
|
|
147
|
-
|
|
148
141
|
Never put a token in `config.json`, a ticket or a chat.
|
|
149
142
|
|
|
150
143
|
The shipped `slack-app-manifest.yml` also grants the `lists:read` and `lists:write` bot scopes, so a bot can read and write, with its own bot token, a Slack List it has been given access to, such as one list shared by all bots. CSCB itself makes no Lists call. An app created from an earlier manifest must have both scopes added and be re-installed; until then, every Lists call fails with `missing_scope`.
|
|
@@ -166,8 +159,6 @@ Any number of personas may list the same channel, each with its own `delivery`.
|
|
|
166
159
|
- **Off:** DMs to the persona's app are not delivered. The server logs one `persona-dm-dropped` line naming the persona and `dm.enabled`. The persona never opens, reads or posts in a DM.
|
|
167
160
|
- **Group DMs** (DMs with more than one person) are never delivered to any persona, whatever `dm.enabled` says.
|
|
168
161
|
|
|
169
|
-
In declarative mode, a persona with no `channels` must have `dm.enabled` set to `true`.
|
|
170
|
-
|
|
171
162
|
`dm.contact` is the person who receives the persona's prompts and notices when its destination is `"dm"`: in declarative mode when `permission_prompts` is `"dm"`, and in fungible mode when `invited.permission_prompts` is `"dm"` or unset. In either mode a `"dm"` destination needs `dm.enabled: true` and a `dm.contact`. `dm.contact` gates nothing else: it doesn't limit who can DM the persona.
|
|
172
163
|
|
|
173
164
|
Starting a DM with a user (a `reply` to a user ID, or a persona whose destination is `"dm"`) needs the `im:write` bot scope, which the shipped `slack-app-manifest.yml` grants. A `"dm"` destination always opens its DM through Slack before it first posts there, even when the DM already exists. For an app created from an earlier manifest, a `"dm"` persona's prompts and notices are not posted, and a `reply` to a user ID fails with `missing_scope`, until the scope is added and the app is re-installed.
|
|
@@ -176,12 +167,12 @@ Receiving DMs and replying in an existing DM (a `D…` ID) work without the scop
|
|
|
176
167
|
|
|
177
168
|
### Permission prompts
|
|
178
169
|
|
|
179
|
-
This section applies in declarative mode, where `permission_prompts` is required; for fungible mode, see [Permission prompts in fungible mode](channel-modes.md#permission-prompts-in-fungible-mode). It is the persona's destination: its permission prompts and its server notices, such as
|
|
170
|
+
This section applies in declarative mode, where `permission_prompts` is required; for fungible mode, see [Permission prompts in fungible mode](channel-modes.md#permission-prompts-in-fungible-mode). It is the persona's destination: its permission prompts and its server notices, such as *Message not saved* notices and restart-limit warnings, are posted there as the persona.
|
|
180
171
|
|
|
181
172
|
- **A channel ID:** one of the persona's own `channels`. Everyone in that channel sees the prompts and notices.
|
|
182
173
|
- **`"dm"`:** a DM from the persona's app to its `dm.contact`. It needs `dm.enabled: true` and a `dm.contact`.
|
|
183
174
|
|
|
184
|
-
|
|
175
|
+
Choose a destination with [channel delivery](#channel-delivery) in mind. If two personas each receive every message in the other’s destination and storing fails, the notices stop after one *Message not saved* each per episode. Further failures are counted in `server.log` (see [Troubleshooting](troubleshooting.md#troubleshooting)).
|
|
185
176
|
|
|
186
177
|
[Permission Relay](bots.md#permission-relay) covers how prompts are delivered and answered, and [Troubleshooting](troubleshooting.md#troubleshooting) what happens when Slack refuses a post to the destination.
|
|
187
178
|
|
|
@@ -194,7 +185,6 @@ Give each persona a destination that no other persona receives every message in,
|
|
|
194
185
|
```sh
|
|
195
186
|
CLAUDE_CONFIG_DIR=~/.claude-reviewer claude auth login --claudeai
|
|
196
187
|
```
|
|
197
|
-
|
|
198
188
|
Use `--console` instead of `--claudeai` for a Console account. When neither the persona nor the top level sets one, Claude's own default applies.
|
|
199
189
|
|
|
200
190
|
- A directory that doesn't exist yet is fine when its parent exists.
|
package/docs/migration.md
CHANGED
|
@@ -138,7 +138,7 @@ For a host whose state has not yet moved to the current directory, **steps 7 and
|
|
|
138
138
|
|
|
139
139
|
1. **The go-ahead (operator action).** Confirm that the go-ahead for this host still stands, and record it in the switch-over log.
|
|
140
140
|
|
|
141
|
-
**No go.** If there is no go-ahead, there is no Phase 1 install. Reinstall the previous CSCB, the version step 1 recorded, and restore its version-appropriate files at the paths saved in step 1. On an existing-persona host (0.11.0 or later), restore the persona configuration, credentials files, crontable, `/interject` callers and saved state, including `config.json.last-applied` and `retired-keys.json` when backed up. On a pre-persona host (0.10.x), restore the `config.json`, crontable, `/interject` callers, `access.json` and Slack token environment variables saved in step 1. Start it on the previous compatible CLI and re-enable its autostart (operator action). Every other agent this step already stopped is started again on the previous compatible CLI
|
|
141
|
+
**No go.** If there is no go-ahead, there is no Phase 1 install. Reinstall the previous CSCB, the version step 1 recorded, and restore its version-appropriate files at the paths saved in step 1. On an existing-persona host (0.11.0 or later), restore the persona configuration, credentials files, crontable, `/interject` callers and saved state, including `config.json.last-applied` and `retired-keys.json` when backed up. On a pre-persona host (0.10.x), restore the `config.json`, crontable, `/interject` callers, `access.json` and Slack token environment variables saved in step 1. Start it on the previous compatible CLI and re-enable its autostart (operator action). Every other agent this step already stopped is started again on the previous compatible CLI through its launcher (operator action). The runbook stops there.
|
|
142
142
|
2. **Stop every other agent (operator action).** Otherwise, before the install, disable everything that starts agents and stop every other agent on the host: orchestrators' workers, hand-started sessions and sessions with an `agent-director serve`. Stop every long-running AD process and every caller, including TypeScript clients, before the installer runs. Use the still-installed CLI's `kill` and `find-missing` to reconcile stopped agents, using step 6's version-specific identifiers and flags. Confirm with `agent-director list` that all rows are non-live (`ended` or `missing`), with a read-only `tmux ls` that no agent session is left, and with process checks that all workers, callers, serves and launchers are stopped. Record it in the switch-over log. An unresolved live row or a process that cannot be stopped means "no go": do not run the installer, delete rows or edit SQL to fake a terminal state. No agent may run across the upgrade; the installer enforces this with `ErrAgentsLive`.
|
|
143
143
|
3. **Back up the store and locate the original install.** Run from a shell agent-director did not start, in the directory the original installer ran in (or the same repository). Before the move, the default store is `~/.agent-director/state.db`. For an external `[store] db_path`, save `state.db`, `state.db-wal` and `state.db-shm` while all callers are stopped; the installer migrates an external store in place and rollback needs that copy. Keep the original installer flags and registrations in the switch-over log.
|
|
144
144
|
4. **Install released agent-director 0.12.0 with its own installer.** Follow [agent-director's installer instructions](https://github.com/gabemahoney/agent-director/blob/v0.12.0/skills/install-agent-director/SKILL.md). Do not copy binaries by hand or create templates in the new directory first. The installer moves `~/.agent-director` to `~/.config/agent-director`, keeps the original as `~/.agent-director.pre-move-<timestamp>`, migrates the store to schema 9, and migrates config and templates. Preserve that kept directory until rollback is no longer wanted. Fix any manual PATH, hook or MCP entries the installer reports, including entries under other Claude config directories; confirm `agent-director version` reports 0.12.0 or later and the new `config_dir`.
|
|
@@ -148,7 +148,7 @@ For a host whose state has not yet moved to the current directory, **steps 7 and
|
|
|
148
148
|
|
|
149
149
|
#### Step 9: Start the other agents again
|
|
150
150
|
|
|
151
|
-
Every other agent on the host is started again
|
|
151
|
+
Every other agent on the host is started again through its launcher (operator action), which also starts its `serve` processes on the Phase 1 binary.
|
|
152
152
|
|
|
153
153
|
#### Step 10: Start the new CSCB
|
|
154
154
|
|
|
@@ -159,15 +159,17 @@ Every other agent on the host is started again by its owner (operator action), w
|
|
|
159
159
|
```sh
|
|
160
160
|
agent-director get --instance-id cscb_<key>
|
|
161
161
|
```
|
|
162
|
-
|
|
162
|
+
Record the row's state, labels and `session_id`. Keep the persona's key, `working_directory` and `claude_config_dir` unchanged, and confirm on the pinned tmux socket that no leftover session has the persona's session name. Record the results in the switch-over log.
|
|
163
163
|
|
|
164
164
|
Start the server:
|
|
165
165
|
```sh
|
|
166
166
|
claude-slack-channel-bots start
|
|
167
167
|
```
|
|
168
|
-
|
|
168
|
+
Every earlier-release bot starts a fresh conversation once, including bots on a host already running personas (0.11.0 or later). Memory files are kept; the old conversation context is not retained. The replacement row carries `tag_prompt=1`. A row without the marker is never resumed or typed into. A live row read `working` or `check_permission` is left alone until eligible; one read `waiting`, `pending` or `ask_user` is replaced through the checked recovery path.
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
On a host switching over from a pre-persona release (0.10.x), the first start has no last-applied record yet, so it checks `config.json`, records it and applies it; after that, edits wait until you apply them (see [Reload](reload.md#reload)). Each persona starts fresh once during this upgrade. Pre-persona rows are kept and never resumed.
|
|
171
|
+
|
|
172
|
+
Check each replacement with the `agent-director get` command above: it must carry the marker and a new `session_id`. Record the result in the switch-over log. Later restarts resume the marked replacement when `resume_enabled` is enabled, the row is `ended` or `missing`, its current `session_id` is non-empty and `transcript_status: present`. For that later resume, check `server.log` for `spawnForPersona: attempting resume for …`. agent-director 0.12.0 resumes only the current session: it no longer falls back to an older session's transcript when the current one is missing.
|
|
171
173
|
|
|
172
174
|
A host already using the current state directory keeps that path; with `SLACK_STATE_DIR` set, nothing moves. On a pre-persona host (0.10.x) still using the old default path, this start also moves the old state directory: `~/.claude/channels/slack` is renamed to `~/.config/claude-slack-channel-bots`, a symbolic link to it is left at the old name, and the server logs one `[slack] state-migrate: moved the state directory from …` line to `~/.config/claude-slack-channel-bots/server.log`. Edit `config.json` and the other state files through `~/.config/claude-slack-channel-bots` after it. An existing-persona host still using an earlier default state directory follows the state-directory move described in [Migration](#migration); keep its configuration and credentials paths intact.
|
|
173
175
|
3. **The post-install check (operator action).** Once every agent has been started again, confirm that `agent-director list --state pending` shows a `launch_started_at` on every row. Record the result in the switch-over log. A row without one is a human's to look at, and the persona whose row it is is held (see "A persona posts a *Held: launch start not recorded* notice" and "How a hold ends" in [Troubleshooting](troubleshooting.md#troubleshooting)).
|
|
@@ -245,9 +247,9 @@ Check each result, then run `agent-director find-missing` and switch-over step 5
|
|
|
245
247
|
|
|
246
248
|
1. **Stop every other agent (operator action).** Disable everything that starts agents. Before the previous binary is restored, stop every other agent on the host and every long-running agent-director process: orchestrators' workers, hand-started sessions and sessions with an `agent-director serve`. A `serve` runs inside its Claude session, so that session is stopped too. Stop every caller, including TypeScript clients, as well. Confirm as in [switch-over step 8](#step-8-install-agent-director-phase-1): `agent-director list` shows each stopped agent's row `ended` or `missing`, and a read-only `tmux ls` shows no agent session left. Record it in the switch-over log.
|
|
247
249
|
|
|
248
|
-
The restore does not begin while any agent runs. Every row must be non-live and every worker, session, caller, serve and launcher stopped before restoration. An unresolved row means no go; do not delete rows or edit SQL to fake a terminal state. If one cannot be stopped, the rollback stops as step 4 says, and every agent this step stopped is started again on Phase 1
|
|
250
|
+
The restore does not begin while any agent runs. Every row must be non-live and every worker, session, caller, serve and launcher stopped before restoration. An unresolved row means no go; do not delete rows or edit SQL to fake a terminal state. If one cannot be stopped, the rollback stops as step 4 says, and every agent this step stopped is started again on Phase 1 (operator action).
|
|
249
251
|
2. **Restore the previous agent-director** using [the released 0.12.0 way-back procedure](https://github.com/gabemahoney/agent-director/blob/v0.12.0/docs/operator-actions.md#going-back-to-the-previous-release). Set the new directory aside and restore the kept `~/.agent-director.pre-move-*` directory to `~/.agent-director`; restore any external store from its pre-upgrade copy. Restore registrations, hooks and paths with the previous release's own installer and its original flags, following that procedure. Schema 9 has no downgrade recipe: a binary swap or SQL column removal cannot roll it back. Data written since the upgrade is lost. Restart only callers compatible with the restored CLI.
|
|
250
|
-
3. **Start the other agents again (operator action).** Every other agent on the host is started again
|
|
252
|
+
3. **Start the other agents again (operator action).** Every other agent on the host is started again on the previous binary, which also starts its `serve` processes on it.
|
|
251
253
|
|
|
252
254
|
#### Step 7: Revert the orchestrator prompt's hold wording
|
|
253
255
|
|
|
@@ -273,6 +275,8 @@ claude-slack-channel-bots start
|
|
|
273
275
|
|
|
274
276
|
### Upgrading to personas
|
|
275
277
|
|
|
278
|
+
After upgrading, every bot starts one fresh conversation; memory files are kept. Remove the Claude Code version pin after the upgrade.
|
|
279
|
+
|
|
276
280
|
This major version accepts only the persona configuration format. These are the configuration steps of an upgrade from an earlier major version. Do them inside the switch-over runbook, not on their own: steps 1, 2, 3, 5 and 6 (the persona file, names, reply settings, Slack apps and who can reach each persona) at [step 1 of the switch-over](#step-1-check-the-host-and-stage-the-release), and steps 4, 7 and 8 (credentials files, crontable and `/interject` callers) at [step 7 of the switch-over](#step-7-install-the-new-cscb-without-starting-it). At switch-over step 1, these steps are written into the separate staged file, not `config.json`; step 7 puts that file in place as `config.json`. Until the new CSCB's first start, the new CLI keeps using the old state directory, `~/.claude/channels/slack`, so the persona `config.json`, the credentials files and the crontable go there; the first start moves that directory to `~/.config/claude-slack-channel-bots`.
|
|
277
281
|
|
|
278
282
|
1. **Rewrite `config.json` by hand.** A configuration from an earlier major version stops the server at start, with an error that names the offending setting and says the configuration must be converted to personas. Nothing is converted automatically and the file is not changed. Write a `personas` list as described in [Personas (config.json)](configuration.md#personas-configjson); the server-wide settings keep their names. The `debug-slack-channel-bots` skill covers this error under "Pre-persona configuration".
|
|
@@ -286,12 +290,13 @@ This major version accepts only the persona configuration format. These are the
|
|
|
286
290
|
|
|
287
291
|
### Downgrading to an earlier release
|
|
288
292
|
|
|
289
|
-
|
|
293
|
+
Earlier releases ignore this release’s queue and can resume a bot without their required channels launch, leaving it unable to receive messages. Follow all four phases in order, including when the configuration already fits the target release.
|
|
290
294
|
|
|
291
|
-
1. **
|
|
292
|
-
2. **
|
|
293
|
-
|
|
294
|
-
|
|
295
|
+
1. **Stop sending and empty the queues.** Wait until `server.log` shows every received item delivered. Otherwise explicitly accept that undelivered messages are lost when using the earlier release.
|
|
296
|
+
2. **Disable resume on the current release.** Set `resume_enabled` to `false` in `config.json`, [confirm the pending change](reload.md#confirming-a-change), and verify `config.json.last-applied` holds `resume_enabled: false`. Keep the current server running until this is applied.
|
|
297
|
+
If the target release does not support these settings, first give every persona a valid declarative section (`permission_prompts`, and `channels` unless `dm.enabled` is `true`; see [Load-time rules](configuration.md#load-time-rules)), remove `allow_invited_channels` and every persona’s `invited`, confirm, and ensure the last-applied record holds neither. These precautions do not replace any phase.
|
|
298
|
+
3. **Stop with the bots, then install and pin the earlier build.** Run `claude-slack-channel-bots stop --stop-bots` and verify it succeeds, install the earlier build (for example `bun install -g claude-slack-channel-bots@<version>`), then restore its required Claude Code version pin before starting it.
|
|
299
|
+
4. **Start the earlier release fresh with channels, then restore resume.** Run `claude-slack-channel-bots start`; verify every bot starts a new conversation with that release’s channels launch and no configuration load error. Only then set `resume_enabled` back to its previous value and confirm the change. The earlier build ignores `channel-delivery.json`; leave it in place.
|
|
295
300
|
|
|
296
301
|
---
|
|
297
302
|
|
package/docs/reload.md
CHANGED
|
@@ -14,6 +14,8 @@ These files sit in the same directory as `config.json` (`~/.config/claude-slack-
|
|
|
14
14
|
| `retired-keys.json` | The retired-key record: the persona keys the server holds as retired. A confirmed change records the key of each removed persona (a renamed persona's old key included) and of each persona whose `name`, `credentials_file` or `working_directory` changed, before `config.json.last-applied` is rewritten. A server start also records, before its clean-up of old instances ends any of them, the key of every agent-director instance labelled with a persona that is not in the applied configuration (for example, a persona that `config.json` no longer names, at a start after `config.json.last-applied` was deleted; see [Start rules](#start-rules)), so a persona added back later with that key starts fresh. If that write fails, the start logs it in `server.log`, holds those keys as retired until the server stops, and records them again at the next start. Only the server writes it, never the CLI. It is kept across restarts and is absent until a key is first recorded. A key's entry is removed on its own once the persona's new session is running. If it can't be read, the server doesn't start; moving it aside is the fix (see `retired-keys-unreadable` under [Startup errors](troubleshooting.md#startup-errors)). |
|
|
15
15
|
| `channel-delivery.json` | The stored-choice file: each persona's stored channel-delivery choices (see [Channel delivery in fungible mode](channel-modes.md#channel-delivery-in-fungible-mode)), by persona key and channel, with each persona's `name`, `credentials_file` and `working_directory`. It holds no token and no message text. Only the server reads and writes it, never the CLI. It is absent until the first accepted `set_channel_delivery` call, so it is never created in declarative mode. Never edit it by hand. The server reads it once at each start. A persona's choices are dropped when a confirmed change retires its key (the persona is removed, or its `name`, `credentials_file` or `working_directory` changes), and at a start when its key is not an applied persona, its `name`, `credentials_file` or `working_directory` differs from the one stored, or `retired-keys.json` holds its key as retired and the persona's new session has not yet begun; each drop logs a `[slack] channel-delivery:` line. If the file can't be read, parsed or validated at a start, the server leaves it in place, logs one [`channel-delivery-unreadable`](../skills/debug-slack-channel-bots/SKILL.md#channel-delivery-unreadable) line, and starts and runs every persona, but for that run no stored choice applies: in fungible mode every channel is at `mentions`, `set_channel_delivery` is refused, and nothing is written to the file or dropped from it. To fix it, move the file aside, then restart the server; this discards every stored choice. If you restore that same file in place instead of moving it aside, a persona removed and added back with the same `name`, `credentials_file` and `working_directory` while it was unreadable gets its earlier choices again. |
|
|
16
16
|
|
|
17
|
+
| `queue/<key>.jsonl` | The persona’s durable queue, holding message text until delivery. Removing a persona retains its queue file; delivery resumes only when the same key returns. |
|
|
18
|
+
|
|
17
19
|
## Start rules
|
|
18
20
|
|
|
19
21
|
| At start | What happens |
|
|
@@ -60,7 +62,7 @@ claude-slack-channel-bots: pending configuration change (written by the server)
|
|
|
60
62
|
fingerprint: sha256:<64 hex digits>
|
|
61
63
|
|
|
62
64
|
A configuration change is pending; nothing has been applied. personas: 1 added, 1 removed, 0 destructively modified, 1 modified in place, 1 with changed credentials; server-wide settings: 2 changed.
|
|
63
|
-
DESTRUCTIVE: persona "scribe" (key=scribe) is removed: the persona will be retired: its session stopped and never resumed.
|
|
65
|
+
DESTRUCTIVE: persona "scribe" (key=scribe) is removed: the persona will be retired: its session stopped and never resumed; 2 open queued items kept, delivered only if a persona with the same key is added again.
|
|
64
66
|
persona "helper" (key=helper) is added but cannot come up: working directory does not exist.
|
|
65
67
|
persona "planner" (key=planner): channels changed: applied in place immediately, instance kept.
|
|
66
68
|
persona "reviewer" (key=reviewer): credentials file "/home/operator/.config/claude-slack-channel-bots/reviewer-credentials.json" changed: a new connection opens, then the old one closes, instance kept.
|
|
@@ -156,7 +158,7 @@ To make them take effect sooner, wait for the `reload-applied` line, then run `c
|
|
|
156
158
|
|
|
157
159
|
## Destructive changes
|
|
158
160
|
|
|
159
|
-
`DESTRUCTIVE:` lines in the preview name the personas the change retires when you confirm it (see the table above). A retired persona's session is stopped and never resumed. Read the preview before you rename it. There is no undo. A persona removed and added again with the same key, or renamed and then renamed back, always starts a new conversation, even with a server restart in between: its old conversation is never resumed. A persona brought up from its new entry after a `credentials_file` path or `working_directory` change starts fresh the same way.
|
|
161
|
+
`DESTRUCTIVE:` lines in the preview name the personas the change retires when you confirm it (see the table above). A retired persona's session is stopped and never resumed. Its queue is retained: add a persona with the same key to deliver its queued messages. Read the preview before you rename it. There is no undo. A persona removed and added again with the same key, or renamed and then renamed back, always starts a new conversation, even with a server restart in between: its old conversation is never resumed. A persona brought up from its new entry after a `credentials_file` path or `working_directory` change starts fresh the same way.
|
|
160
162
|
|
|
161
163
|
CSCB never deletes an agent-director row, and it checks the result of every kill it makes. A teardown ends the old session, checks the result, and keeps the row; a removed persona's row stays until agent-director's `expire` removes it, and is never resumed. A kill agent-director can't carry out right now is tried up to 3 times, 2 s apart. When the kill still fails (for example, the worker outlived each of its kills), `server.log` shows the teardown's line for it, which ends `— the row is kept; nothing latches and no retry timer is armed …`. The persona's new session is then not started over the old one: the bring-up after a `credentials_file` path or `working_directory` change ends the old session first and starts the new conversation only once that succeeds. A renamed persona's new name is brought up only once the old instance has ended, and so is any other persona that shares the old instance's working directory; until then a Claude Code session started from that directory is refused (see "A persona waits on an old instance in its working directory" in [Troubleshooting](troubleshooting.md#troubleshooting)). The old conversation is never resumed either way.
|
|
162
164
|
|
|
@@ -170,7 +172,7 @@ To fix it, wait for the server to write `config.json.pending` again (within abou
|
|
|
170
172
|
|
|
171
173
|
## Size limit
|
|
172
174
|
|
|
173
|
-
`config.json`, the files beside it and each credentials file are read only up to 64 KiB; a larger file is treated as unreadable. The retired-key record (`retired-keys.json`) and the stored-choice file (`channel-delivery.json`) have no size cap: only the server writes them. If a very large change makes `config.json.pending` itself larger, its rename is refused as stale, so split the change into smaller edits.
|
|
175
|
+
`config.json`, the files beside it and each credentials file are read only up to 64 KiB; a larger file is treated as unreadable. The retired-key record (`retired-keys.json`) and the stored-choice file (`channel-delivery.json`) have no size cap: only the server writes them. The queue is also read in full with no size cap (`queue/<key>.jsonl`). If a very large change makes `config.json.pending` itself larger, its rename is refused as stale, so split the change into smaller edits.
|
|
174
176
|
|
|
175
177
|
## No reload command
|
|
176
178
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Scheduled Prompts (cscb_cron)
|
|
2
2
|
|
|
3
|
-
The server fires scheduled prompts into personas once per minute, reading them from a crontable. Each fire is
|
|
3
|
+
The server fires scheduled prompts into personas once per minute, reading them from a crontable. Each fire is stored in the target persona’s queue as its own injected message.
|
|
4
4
|
|
|
5
5
|
## The crontable
|
|
6
6
|
|
|
@@ -91,11 +91,11 @@ The prompt-file path resolves as follows:
|
|
|
91
91
|
|
|
92
92
|
## How fires appear
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
The server directly queues a scheduled fire only for the target persona, as an injected message with no Slack conversation attached. Its `sender` label is `cscb-cron:<prompt-file-basename>` — for a prompt file `standup.md` the sender is `cscb-cron:standup`. This distinguishes a cron tick from a human and from peer-bot traffic. The [Slack Reply Guard](bots.md#slack-reply-guard-stop-hook) never reminds a bot to reply to a scheduled prompt: like every injected message, a fire carries no `via`. Because a fire carries no channel, a prompt file that wants a Slack reply must say where to post it. Each schedule queues its own message independently, so when several schedules match the same minute for the same persona each one is its own queued item.
|
|
95
95
|
|
|
96
96
|
## The cron log
|
|
97
97
|
|
|
98
|
-
Every fire outcome is recorded in the cron log at `cron_log_path` (default `<config dir>/cron.log`, beside `config.json` wherever the crontable is; override with the `cron_log_path` key). Each attempt writes one line per persona targeted (showing the target as first written in the crontable), plus a per-fire summary line carrying `
|
|
98
|
+
Every fire outcome is recorded in the cron log at `cron_log_path` (default `<config dir>/cron.log`, beside `config.json` wherever the crontable is; override with the `cron_log_path` key). Each attempt writes one line per persona targeted (showing the target as first written in the crontable), plus a per-fire summary line carrying `queued=N failed=M` counts. The log is plain text, so `grep queue-failed cron.log` yields readable lines.
|
|
99
99
|
|
|
100
100
|
By default the log only grows. To cap it, set `cron_log_max_bytes` in `config.json`: once the log passes that many bytes, its oldest whole lines are dropped, so the oldest fires disappear from it while the newest stay. There is no rotated copy; copy the log elsewhere if you need a longer history. Don't move or delete it yourself: the server keeps writing to the old file until that file passes the cap. A changed cap takes effect at the next server start.
|
|
101
101
|
|
|
@@ -103,14 +103,13 @@ The `outcome` field of each line is one of these classes:
|
|
|
103
103
|
|
|
104
104
|
| Outcome | What happened | What to do |
|
|
105
105
|
|---|---|---|
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
106
|
+
| `queued` | The prompt was durably stored for the target persona. | Nothing — storage succeeded; this is not a delivery confirmation. |
|
|
107
|
+
| `queue-failed` | The prompt could not be stored. | Check storage availability and permissions in the server log. |
|
|
108
108
|
| `unknown-persona` | No persona in the applied configuration has that name or key. | Correct the target in the crontable. |
|
|
109
109
|
| `prompt-missing` | The prompt file did not exist at fire time. | Create the file or correct its path in the crontable. |
|
|
110
110
|
| `prompt-unreadable` | The prompt file existed but could not be read (see the `errno`). | Fix file permissions or the path. |
|
|
111
111
|
| `prompt-oversize` | The prompt exceeds the 32KB `/interject` cap and was skipped, never truncated. | Shorten the prompt file. |
|
|
112
112
|
| `parse-error` | The crontable line could not be parsed. | Fix the line — see [Crontable format](#crontable-format). |
|
|
113
|
-
| `http-error` | The localhost POST hit an unexpected HTTP status or a network failure. | Check that the server is listening on loopback (see the `bind` note below) and inspect the `errno`/`status` in the line. |
|
|
114
113
|
| `fanout-deferred` | A line with no persona list (all-bots) was matched but not delivered. | Add a persona list to the line — all-bots delivery is not built yet. |
|
|
115
114
|
|
|
116
115
|
Each load of the crontable, at start and after each edit, writes one line with its counts. At start it is `scheduler started, N schedules loaded, M undeliverable (no persona list)`; after an edit it is `crontable reloaded, N schedules, M undeliverable (no persona list)`. `M` counts the lines that will never be delivered, and it is written even when it is 0.
|
|
@@ -129,13 +128,12 @@ grep undeliverable ~/.config/claude-slack-channel-bots/cron.log
|
|
|
129
128
|
|
|
130
129
|
## Delivery semantics
|
|
131
130
|
|
|
132
|
-
- **
|
|
131
|
+
- **Accepted fires stay queued.** A configured persona can accept prompts while it is down or disconnected. Every accepted fire is an independent item, never merged or dropped, and delivered in FIFO order across all sources, carrying its fire timestamp as `ts`; a failed store is logged as `queue-failed` and is not an accepted item.
|
|
133
132
|
- **Missed fires are skipped, not caught up.** While the server is down, no scheduled prompts fire, and they are not replayed on restart. The `scheduler started, N schedules loaded, M undeliverable (no persona list)` line in the cron log marks when scheduling resumed, bounding the outage window.
|
|
134
133
|
- **Edits take effect within a minute — no restart.** The scheduler checks the crontable fresh on every tick, so an edit by hand or a line appended by a bot starts (or stops) firing within about a minute. The server is never restarted for a schedule change.
|
|
135
134
|
- **Deleting the crontable stops all schedules.** Nothing fires from that moment, a WARN appears in the cron log, and the server re-creates the file empty (with its header) within about two minutes — detection and re-creation happen on separate once-a-minute passes, so the re-create lands up to two tick boundaries after the deletion. Add lines back and they schedule on the next check.
|
|
136
135
|
- **Server-local time.** Cron expressions are evaluated in the server's local timezone.
|
|
137
136
|
- **A line without a persona list is never delivered.** Each load logs a `warn` naming it and counts it undeliverable; each time it matches, it is logged `fanout-deferred` and dropped. Give the line a persona list to have it fire.
|
|
138
|
-
- **`bind` must include loopback.** The scheduler delivers via `127.0.0.1`, so a `bind` set to a single non-loopback interface makes every fire fail with `http-error`. Use the default `127.0.0.1` or `0.0.0.0`.
|
|
139
137
|
|
|
140
138
|
## Bot self-scheduling
|
|
141
139
|
|
package/docs/server-settings.md
CHANGED
|
@@ -8,27 +8,27 @@ These top-level fields apply to the whole server. A confirmed change to one take
|
|
|
8
8
|
|
|
9
9
|
| Field | Type | Default | Description |
|
|
10
10
|
|---|---|---|---|
|
|
11
|
-
| `bind` | string | `"127.0.0.1"` | Interface the HTTP server binds to. Use `"0.0.0.0"` to expose on all interfaces.
|
|
11
|
+
| `bind` | string | `"127.0.0.1"` | Interface the HTTP server binds to. Use `"0.0.0.0"` to expose on all interfaces. Scheduled prompts directly enter the queue and need no HTTP or loopback. [`clear-latch`](cli.md#claude-slack-channel-bots-clear-latch) still needs loopback (the default, or `0.0.0.0`). |
|
|
12
12
|
| `port` | number | `3100` | Port the HTTP server listens on. |
|
|
13
|
-
| `session_restart_delay` | number | `60` | Seconds to wait before auto-restarting a session that crashed or disappeared. A session that ended cleanly (an `agent-director pause`, a `/exit`) is relaunched about 3 s after it disconnects instead, unless the persona's last launch failed or it had such a fast relaunch within the last `session_restart_delay` seconds (see "Session not restarting after crash" in [Troubleshooting](troubleshooting.md#troubleshooting)). Set to `0` to disable auto-restart, clean exits included, and the health check's reconnects (not the retries of a persona agent-director refused; see [Troubleshooting](troubleshooting.md#troubleshooting)); a persona whose session runs but can't receive messages then gets a *Not connected*
|
|
13
|
+
| `session_restart_delay` | number | `60` | Seconds to wait before auto-restarting a session that crashed or disappeared. A session that ended cleanly (an `agent-director pause`, a `/exit`) is relaunched about 3 s after it disconnects instead, unless the persona's last launch failed or it had such a fast relaunch within the last `session_restart_delay` seconds (see "Session not restarting after crash" in [Troubleshooting](troubleshooting.md#troubleshooting)). Set to `0` to disable auto-restart, clean exits included, and the health check's reconnects (not the retries of a persona agent-director refused; see [Troubleshooting](troubleshooting.md#troubleshooting)); a persona whose session runs but can't receive messages then gets a *Not connected* notice at its destination (see [Troubleshooting](troubleshooting.md#troubleshooting)). A Slack message also triggers reconnect after MCP disconnect; at `0` it does not, and messages wait until reconnect or restart. At any value, a persona reported as `working` that the server can't prove idle for 10 minutes gets a *Not connected* notice. Must be non-negative. |
|
|
14
14
|
| `health_check_interval` | number | `120` | Seconds between periodic liveness polls. Set to `0` to disable; the retries of a persona agent-director refused still run. Must be non-negative. |
|
|
15
15
|
| `exit_timeout` | number | `120` | Seconds to wait for a managed Claude Code session to exit gracefully during `clean_restart` or `stop --stop-bots` before force-killing it through agent-director. |
|
|
16
16
|
| `stop_timeout` | number | `30` | Seconds to wait for the server process to exit after `SIGTERM` before escalating to `SIGKILL`. |
|
|
17
17
|
| `mcp_config_path` | string | `~/.claude/slack-mcp.json` | Path to the MCP config file passed to Claude Code when launching managed sessions. |
|
|
18
|
-
| `append_system_prompt_file` | string | — |
|
|
19
|
-
| `system_prompt_mode` | string | `"append"` | Controls how `append_system_prompt_file` is applied. `"append"`: the custom prompt file is appended on top of `CLAUDE.md` (default, current behavior). `"none"`:
|
|
18
|
+
| `append_system_prompt_file` | string | — | One shared file supplies obedience instructions for every persona: say whose instructions each bot follows here. Its text is composed with CSCB’s tag description for `--append-system-prompt-file`. Missing file silently skipped. See `skills/EXAMPLE_CLAUDE.md` for a template. |
|
|
19
|
+
| `system_prompt_mode` | string | `"append"` | Controls how `append_system_prompt_file` is applied. `"append"`: the custom prompt file is appended on top of `CLAUDE.md` (default, current behavior). `"none"`: the operator file is ignored even if set. CSCB’s tag description is always carried, including in `none` mode. Use `"none"` when the project's `CLAUDE.md` already contains everything the bot needs. |
|
|
20
20
|
| `fresh_system_prompt` | boolean | `true` | When `true` (default), every bot launches with `--system-prompt-snapshot off`, so a restarted or resumed bot loads the current system prompt file and `CLAUDE.md` and keeps its conversation, whatever `system_prompt_mode` is. When `false`, the flag is omitted. A change takes effect at the next server start. Needs a Claude Code that has `--system-prompt-snapshot` (confirmed on 2.1.280); on an older one set it to `false`. A bot keeps the launch arguments it was started with on every resume, so a change reaches a bot only when it next starts a new conversation; a `clean_restart` that resumes conversations does not apply it to those bots. |
|
|
21
21
|
| `cozempic_prescription` | string | `"standard"` | Cozempic cleaning intensity before resume. Valid values: `gentle`, `standard`, `aggressive`. Has no effect if cozempic is not installed. |
|
|
22
|
-
| `message_archive_db` | string | — | Path to a SQLite DB where every inbound Slack message is archived in real time. Parent directories are created if missing; schema is initialized on first open. Compatible with the `archive-messages.py` backfill script — both can write concurrently. Each channel's name is looked up through the receiving persona's Slack app, which needs the `channels:read` and `groups:read` bot scopes that the shipped `slack-app-manifest.yml` grants.
|
|
22
|
+
| `message_archive_db` | string | — | Path to a SQLite DB where every inbound Slack message is archived in real time. Parent directories are created if missing; schema is initialized on first open. Compatible with the `archive-messages.py` backfill script — both can write concurrently. Each channel's name is looked up through the receiving persona's Slack app, which needs the `channels:read` and `groups:read` bot scopes that the shipped `slack-app-manifest.yml` grants. For channel IDs in place of names, see [The message archive shows channel IDs](../skills/debug-slack-channel-bots/SKILL.md#the-message-archive-shows-channel-ids-re-install-each-app-to-gain-channelsread-and-groupsread). A DM's rows keep the DM's `D…` ID whatever the scopes, as a DM has no name; a group DM's rows keep its ID too. Feature is disabled when absent. |
|
|
23
23
|
| `claude_config_dir` | string | — | Default Claude on-disk config directory for every persona. When a persona has one (its own or this default), its session launches with `CLAUDE_CONFIG_DIR='<resolved-path>'` so the bot authenticates against a specific account. `~` is expanded and the path is resolved to absolute. A persona's own `claude_config_dir` overrides this value. When neither is set, Claude's own default applies. Must be non-empty when set. |
|
|
24
|
-
| `resume_enabled` | boolean | `true` | When `true` (default), a bot whose session died — including after a host reboot or pod resume — comes back with its prior conversation history intact instead of starting fresh; after 2 resumes in a row that end before the session starts (a transcript Claude Code can't load), it starts fresh instead (see "Session not restarting after crash" under [Troubleshooting](troubleshooting.md#troubleshooting)). When `false`, a bot is never resumed: on startup and on runtime auto-restart it starts a new conversation on the same instance, even when a stored session exists, and its Claude row retains its AD life and prior sessions.
|
|
25
|
-
| `agent_director_poll_interval_ms` | number | `1000` | Poll interval (ms) for the agent-director permission relay tick. Must be a positive integer in `[200, 3_600_000]`.
|
|
24
|
+
| `resume_enabled` | boolean | `true` | When `true` (default), a bot whose session died — including after a host reboot or pod resume — comes back with its prior conversation history intact instead of starting fresh; after 2 resumes in a row that end before the session starts (a transcript Claude Code can't load), it starts fresh instead (see "Session not restarting after crash" under [Troubleshooting](troubleshooting.md#troubleshooting)). When `false`, a bot is never resumed: on startup and on runtime auto-restart it starts a new conversation on the same instance, even when a stored session exists, and its Claude row retains its AD life and prior sessions. For resume failures, see [Troubleshooting](troubleshooting.md#troubleshooting). |
|
|
25
|
+
| `agent_director_poll_interval_ms` | number | `1000` | Poll interval (ms) for the agent-director permission relay tick. Must be a positive integer in `[200, 3_600_000]`. For configuration errors, see [Troubleshooting](troubleshooting.md#troubleshooting). |
|
|
26
26
|
| `agent_director_call_timeout_ms` | number | `60000` | How long (ms) CSCB waits on each agent-director call it makes for its personas, in the server and in `stop --stop-bots` and `clean_restart`. Must be an integer in `[1000, 3_600_000]`; out-of-range values are rejected at load. It should be greater than the need that agent-director's timing settings give; see [Sizing the agent-director call timeout](agent-director.md#sizing-the-agent-director-call-timeout). |
|
|
27
27
|
| `stop_hook_bootstrap` | boolean | `true` | Default for every persona: whether the persona gets the Slack Reply Guard reminder (see [Slack Reply Guard (Stop hook)](bots.md#slack-reply-guard-stop-hook)). The value applies per persona, from the persona's first launch after the change is applied (see [Reload](reload.md#reload)). A persona's own `stop_hook_bootstrap` overrides this value. Non-boolean values are rejected at startup. |
|
|
28
28
|
| `cron_table_path` | string | `~/.config/claude-slack-channel-bots/crontab` | Path to the crontable for the built-in cron scheduler (`cscb_cron`). When absent, it is `crontab` in the state directory, beside `config.json`: `~/.config/claude-slack-channel-bots/crontab` by default, outside `~/.claude`, so a bot's edit is not a write under `~/.claude`, which Claude Code asks the user to approve. A path you set is used as written, with `~` expanded like other path keys: the server never creates its directory or moves a crontable to it. The resolved path is exported into every managed session as `CSCB_CRONTABLE_PATH` so bots can find the crontable and self-schedule (see [Scheduled Prompts](scheduled-prompts.md#scheduled-prompts-cscb_cron)). Must be a non-empty string when set. |
|
|
29
29
|
| `cron_log_path` | string | `<config dir>/cron.log` | Path to the `cscb_cron` log file. Defaults to `cron.log` in the directory of the loaded `config.json`, wherever the crontable is. `~` is expanded like other path keys. Must be a non-empty string when set. |
|
|
30
30
|
| `cron_log_max_bytes` | number | — | Size cap in bytes for the cron log. Once a line takes the log past the cap, the server drops its oldest whole lines until it fits. The newest line is always kept, even when it alone is larger than the cap. Nothing is rotated or archived, so dropped lines are gone. Absent means no cap: the log only grows. Must be a positive integer when set. |
|
|
31
|
-
| `ack_reaction` | string | — | Emoji name, without colons (for example `"eyes"`),
|
|
31
|
+
| `ack_reaction` | string | — | Emoji name, without colons (for example `"eyes"`), added under each receiving persona's own Slack identity after its bot's transcript confirms the whole message. Queued messages get it after delivery, including messages waiting while the bot is down. Dropped messages and injected messages get none. A successful `reply` with the message's `message_id` removes only that persona's reaction; a reply before delivery suppresses the later reaction. That early-reply memory lasts 24 hours and is forgotten on a server restart, so an already answered message can then keep a reaction. Absent means no acknowledgement. Must be non-empty when set. |
|
|
32
32
|
| `reply_chunk_limit` | number | `4000` | Maximum characters per posted message: the `reply` tool splits longer text into several messages. Must be a positive integer. |
|
|
33
33
|
| `reply_chunk_mode` | `"length"` \| `"newline"` | `"newline"` | How the `reply` tool splits text longer than `reply_chunk_limit`. `length`: hard split at the limit. `newline`: split at newline boundaries within the limit; a single line longer than the limit is posted whole. |
|
|
34
34
|
| `allow_invited_channels` | boolean | `false` | Picks the channel mode for every persona. Absent or `false` is **declarative mode**: each persona's `channels` and top-level `permission_prompts` are in force, and its `invited` section is not read. `true` is **fungible mode**: each persona's `channels` and top-level `permission_prompts` are not read, and its `invited` section is in force. A confirmed change applies in place at once and keeps every session. Non-boolean values are rejected at load. See [Channel modes](channel-modes.md#channel-modes), and read [Who can reach a persona in fungible mode](channel-modes.md#who-can-reach-a-persona-in-fungible-mode) before turning it on. |
|
|
@@ -43,7 +43,7 @@ Runtime options are read from environment variables. None of them is required. S
|
|
|
43
43
|
|---|---|
|
|
44
44
|
| `SLACK_STATE_DIR` | Override the directory where `config.json` and runtime state are stored. Defaults to `~/.config/claude-slack-channel-bots` (see `XDG_CONFIG_HOME` below), outside `~/.claude`, where Claude Code asks the user before every write. The crontable defaults to `crontab` in this directory (see `cron_table_path` under [Server-wide settings](#server-wide-settings)). |
|
|
45
45
|
| `XDG_CONFIG_HOME` | The standard XDG configuration directory. With `SLACK_STATE_DIR` unset and `XDG_CONFIG_HOME` an absolute path, the state directory, and the crontable in it, is `$XDG_CONFIG_HOME/claude-slack-channel-bots` instead of `~/.config/claude-slack-channel-bots`. An empty or relative value is ignored. |
|
|
46
|
-
| `SLACK_DRY_RUN` | Set to `1` (or `true` / `yes`) to start the server without Slack. No credentials file is read and no Slack call is made. Each persona runs with a placeholder identity (`U000DRY_<key>`), and MCP tool calls (`reply`, `react`, etc.) and server notices are logged instead of sent.
|
|
46
|
+
| `SLACK_DRY_RUN` | Set to `1` (or `true` / `yes`) to start the server without Slack. No credentials file is read and no Slack call is made. Each persona runs with a placeholder identity (`U000DRY_<key>`), and MCP tool calls (`reply`, `react`, etc.) and server notices are logged instead of sent. |
|
|
47
47
|
| `CSCB_LOG_MAX_BYTES` | Rotate `server.log` / `clean_restart.log` when the active file reaches this many bytes. Defaults to `10485760` (10 MiB). Values `<= 0` or non-numeric are ignored. |
|
|
48
48
|
| `CSCB_LOG_KEEP` | Number of rotated generations to retain (`server.log.1` … `server.log.N`). Defaults to `5`. Set to `0` to keep none (the log is truncated instead of rolled). Values `< 0` or non-numeric are ignored. |
|
|
49
49
|
| `CSCB_AD_VERBOSE` | Set to a truthy value (`1`, `true`, `yes`, `on`) to restore the agent-director library's per-poll `SubprocessClient: <verb> ok` success dumps in `server.log`. Off by default — these routine dumps are dropped so the log stays readable. Failures and warnings from agent-director always pass through regardless of this flag. Read once at server startup, so it takes effect on server restart. |
|
|
@@ -67,3 +67,11 @@ Claude Code sessions need a config file pointing at the MCP server. A skeleton i
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
If you change `port` or `bind` in `config.json`, the confirmed change takes effect only at the next start (see [Reload](reload.md#reload)), so update the `url` here to match at that restart. The server-managed session launcher uses `mcp_config_path` from the applied configuration to locate this file.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Message text and privacy
|
|
74
|
+
|
|
75
|
+
Undelivered message text is kept until delivery, including a removed persona’s queue. Delivered text is removed within 24 hours. Typed Slack text also remains in the agent’s native transcript and prompt history file; Claude Code keeps these in its config directory.
|
|
76
|
+
|
|
77
|
+
`server.log` retains message text in received-event, dispatch and `/interject` lines. Protect the state and Claude Code config directories and any log copies accordingly.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -18,10 +18,10 @@ The first start recorded the bad setting, such as `port` or `bind`, before it fa
|
|
|
18
18
|
`server.log` shows `Session connected with CWD "<path>" — no matching persona`: the session's working directory is no persona's `working_directory`, compared by real path. A `Session refused: …` line instead means an old instance may still run in that directory (see "A persona waits on an old instance in its working directory"). [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-session-connects-but-has-no-persona).
|
|
19
19
|
|
|
20
20
|
**A persona doesn't come up or doesn't answer**
|
|
21
|
-
The persona is silent
|
|
21
|
+
The persona is silent. Accepted messages remain queued while its instance is unavailable. Find its key on its `persona-start` line in `server.log` and read its lines: each names a class (such as `persona-credentials-invalid` or `persona-directory-missing`) and the file or directory to fix. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-a-persona-doesnt-come-up-or-doesnt-answer).
|
|
22
22
|
|
|
23
23
|
**A persona's instance waits at a startup prompt**
|
|
24
|
-
After each launch the server presses Enter on Claude Code's folder-trust
|
|
24
|
+
After each launch the server presses Enter on Claude Code's folder-trust prompt, for at most the later of 5 minutes and `pending_grace_seconds` plus 60 s, and types nothing else. For any other prompt, attach with `tmux attach -t =slack_bot_<key>` and answer it. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-a-personas-instance-waits-at-a-startup-prompt).
|
|
25
25
|
|
|
26
26
|
**A persona's instance is still starting**
|
|
27
27
|
Normal for a short time after every launch: the server waits it out and never launches a second instance over it. If it hasn't started in time, the persona posts a *Launch stuck* or *Session not starting* notice. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-a-personas-instance-is-still-starting).
|
|
@@ -51,7 +51,7 @@ Slack refused a post to the persona's destination; a `persona-destination-failed
|
|
|
51
51
|
The bot is stuck on a Claude Code prompt that never reached Slack. A human reads it with `agent-director read-pane --instance-id <id>`, ends the session with `agent-director kill --instance-id <id>` and checks the result; on an error, don't delete or respawn, and never use `send-keys`. The server then brings the persona up again. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-bot-appears-dead--posts-a-blocked-on-a-native-claude-code-permission-prompt-warning).
|
|
52
52
|
|
|
53
53
|
**Session not restarting after crash**
|
|
54
|
-
Restarts back off from `session_restart_delay`, and after 5 failed launches in a row the persona posts `SpawnCapReached` and automatic restarts stop. Fix the cause, then restart the server (`claude-slack-channel-bots stop && claude-slack-channel-bots start`) to clear the limit.
|
|
54
|
+
Restarts back off from `session_restart_delay`, and after 5 failed launches in a row the persona posts `SpawnCapReached` and automatic restarts stop. Fix the cause, then restart the server (`claude-slack-channel-bots stop && claude-slack-channel-bots start`) to clear the limit. Accepted messages remain queued while the persona cannot receive them. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-session-not-restarting-after-crash).
|
|
55
55
|
|
|
56
56
|
**A persona is retried after agent-director refuses it**
|
|
57
57
|
The server retries the persona on its own (after 30, 60, 120 and 240 s, then every 300 s) and never counts it toward the restart limit. If it never clears, check that `agent-director version` answers and fix agent-director; no server restart is needed. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-a-persona-is-retried-after-agent-director-refuses-it).
|
|
@@ -60,13 +60,16 @@ The server retries the persona on its own (after 30, 60, 120 and 240 s, then eve
|
|
|
60
60
|
After a removal, rename or path change, or a start whose clean-up could not end an old instance, no persona comes up in that directory until the old instance has ended; the server keeps ending it. If its kill keeps failing, a human follows the "Operator actions" section of agent-director's README. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-a-persona-waits-on-an-old-instance-in-its-working-directory).
|
|
61
61
|
|
|
62
62
|
**Bot alive but silently unresponsive (MCP disconnected)**
|
|
63
|
-
The health check reconnects or relaunches such a bot on its own, within about 3–5 minutes at the default settings. With `session_restart_delay` set to `0`, a *Not connected*
|
|
63
|
+
Accepted messages remain queued; a Slack message triggers the configured reconnect too. The health check reconnects or relaunches such a bot on its own, within about 3–5 minutes at the default settings. With `session_restart_delay` set to `0`, a *Not connected* notice reports it instead. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-bot-alive-but-silently-unresponsive-mcp-disconnected).
|
|
64
64
|
|
|
65
65
|
**Reading the start summary**
|
|
66
66
|
Each start logs one `[slack] startupSessionManager: complete — …` line counting its personas by outcome. `latched`, `retrying`, `waiting on a live-row sequence`, `held on invalid flags` and `fresh as retired keys` are not failures. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-reading-the-start-summary).
|
|
67
67
|
|
|
68
|
-
**
|
|
69
|
-
|
|
68
|
+
**Delivery notices or delivery diagnostic lines**
|
|
69
|
+
For *Message not saved*, check the server disk; for *Delivery stuck* or *Message not taken in*, follow the notice's remedy; for *Messages waiting*, resolve the persona's state; for *Queue record unreadable*, preserve its file and ask the senders to resend. Accepted messages stay queued in order. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-delivery-notices-or-delivery-diagnostic-lines).
|
|
70
|
+
|
|
71
|
+
**A persona posts a *Waiting on a prompt* or *Not connected* notice**
|
|
72
|
+
The session runs but is disconnected; accepted messages are kept until it reconnects. A human attaches with `tmux attach -t =slack_bot_<key>`, deals with anything on screen and, if it stays disconnected, types `/mcp reconnect slack-channel-router`, or restarts the server. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-a-persona-posts-a-waiting-on-a-prompt-or-not-connected-notice).
|
|
70
73
|
|
|
71
74
|
**A persona posts a *Not answering*, *Still not answering* or *Answering again* notice**
|
|
72
75
|
agent-director or tmux is not answering for this persona's session; the server keeps retrying and posts *Answering again* when it recovers. After *Still not answering*, a human checks tmux and agent-director with read-only commands only. [Full entry](../skills/debug-slack-channel-bots/SKILL.md#symptom-a-persona-posts-a-not-answering-still-not-answering-or-answering-again-notice).
|