discoclaw 0.9.0 → 1.0.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/.context/automations.md +151 -0
- package/.context/bot-setup.md +268 -5
- package/.context/dev.md +121 -2
- package/.context/discord.md +104 -0
- package/.context/ops.md +188 -8
- package/.context/runtime.md +144 -34
- package/.context/tasks.md +142 -0
- package/.env.example +83 -17
- package/.env.example.full +70 -4
- package/README.md +159 -19
- package/dist/browser/managed-browser.js +867 -0
- package/dist/browser/managed-browser.test.js +386 -0
- package/dist/canvas/apps.js +291 -0
- package/dist/canvas/artifact-store.js +131 -0
- package/dist/canvas/artifact-store.test.js +57 -0
- package/dist/canvas/canvas-action.js +504 -0
- package/dist/canvas/canvas-action.test.js +431 -0
- package/dist/canvas/file-export.js +100 -0
- package/dist/canvas/file-export.test.js +74 -0
- package/dist/canvas/launch-store.js +207 -0
- package/dist/canvas/launch-store.test.js +115 -0
- package/dist/canvas/runtime-entry.test.js +35 -0
- package/dist/canvas/server.js +653 -0
- package/dist/canvas/server.test.js +352 -0
- package/dist/canvas/shell.js +584 -0
- package/dist/canvas/shell.test.js +14 -0
- package/dist/canvas/tokens.js +84 -0
- package/dist/cli/dashboard.js +6 -2
- package/dist/cli/dashboard.test.js +3 -2
- package/dist/cli/index.js +354 -3
- package/dist/cli/index.test.js +314 -1
- package/dist/cli/init-wizard.js +72 -42
- package/dist/cli/init-wizard.test.js +100 -17
- package/dist/config.js +247 -13
- package/dist/config.test.js +90 -19
- package/dist/cron/cron-prompt.js +67 -2
- package/dist/cron/cron-prompt.test.js +95 -0
- package/dist/cron/cron-sync.js +40 -8
- package/dist/cron/cron-sync.test.js +120 -4
- package/dist/cron/executor.js +130 -35
- package/dist/cron/executor.test.js +165 -0
- package/dist/cron/run-stats.js +15 -1
- package/dist/cron/run-stats.test.js +139 -8
- package/dist/dashboard/api/snapshot.test.js +1 -0
- package/dist/dashboard/auth-probe.js +110 -0
- package/dist/dashboard/auth-probe.test.js +157 -0
- package/dist/dashboard/page.js +980 -626
- package/dist/dashboard/page.test.js +9 -20
- package/dist/dashboard/server-errors.js +1 -1
- package/dist/dashboard/server.js +171 -23
- package/dist/dashboard/server.test.js +311 -12
- package/dist/dashboard/snapshot.js +96 -0
- package/dist/dashboard/snapshot.test.js +177 -0
- package/dist/discord/abort-registry.js +39 -10
- package/dist/discord/action-categories.js +36 -0
- package/dist/discord/action-categories.test.js +57 -0
- package/dist/discord/action-flags.js +1 -0
- package/dist/discord/actions-archive.js +5 -19
- package/dist/discord/actions-bot-profile.js +4 -16
- package/dist/discord/actions-channels.js +17 -72
- package/dist/discord/actions-config.js +106 -57
- package/dist/discord/actions-config.test.js +172 -35
- package/dist/discord/actions-crons.js +88 -80
- package/dist/discord/actions-crons.test.js +142 -0
- package/dist/discord/actions-forge.js +12 -27
- package/dist/discord/actions-forge.test.js +3 -10
- package/dist/discord/actions-guild.js +12 -53
- package/dist/discord/actions-imagegen.js +8 -35
- package/dist/discord/actions-imagegen.test.js +2 -3
- package/dist/discord/actions-loop.js +9 -0
- package/dist/discord/actions-loop.test.js +25 -1
- package/dist/discord/actions-memory.js +41 -25
- package/dist/discord/actions-memory.test.js +91 -6
- package/dist/discord/actions-messaging.js +22 -89
- package/dist/discord/actions-moderation.js +4 -17
- package/dist/discord/actions-plan.js +14 -40
- package/dist/discord/actions-plan.test.js +1 -1
- package/dist/discord/actions-poll.js +2 -9
- package/dist/discord/actions-spawn.js +3 -15
- package/dist/discord/actions-spawn.test.js +4 -5
- package/dist/discord/actions-voice.js +5 -24
- package/dist/discord/actions.js +39 -68
- package/dist/discord/actions.test.js +119 -11
- package/dist/discord/activity-launch.js +49 -0
- package/dist/discord/audit-handler.js +6 -7
- package/dist/discord/audit-handler.test.js +5 -0
- package/dist/discord/browser-command.js +173 -0
- package/dist/discord/browser-command.test.js +140 -0
- package/dist/discord/deferred-runner.js +12 -0
- package/dist/discord/deferred-runner.test.js +20 -1
- package/dist/discord/durable-consolidation.js +12 -3
- package/dist/discord/durable-memory.js +62 -2
- package/dist/discord/durable-memory.test.js +139 -1
- package/dist/discord/forge-auto-implement.js +4 -1
- package/dist/discord/forge-commands.js +8 -8
- package/dist/discord/forge-commands.test.js +3 -0
- package/dist/discord/health-command.js +6 -1
- package/dist/discord/health-command.test.js +94 -0
- package/dist/discord/help-command.js +1 -0
- package/dist/discord/help-command.test.js +6 -0
- package/dist/discord/long-run-watchdog-notice.js +69 -12
- package/dist/discord/long-run-watchdog-notice.test.js +156 -1
- package/dist/discord/long-run-watchdog.js +60 -1
- package/dist/discord/long-run-watchdog.test.js +198 -0
- package/dist/discord/message-coordinator.followup-lifecycle.test.js +383 -22
- package/dist/discord/message-coordinator.js +515 -79
- package/dist/discord/message-coordinator.onboarding.test.js +1 -1
- package/dist/discord/message-coordinator.run-state.test.js +2 -0
- package/dist/discord/message-coordinator.test.js +680 -2
- package/dist/discord/models-command.js +69 -15
- package/dist/discord/models-command.test.js +92 -2
- package/dist/discord/output-common.js +59 -0
- package/dist/discord/output-common.test.js +65 -1
- package/dist/discord/plan-commands.js +24 -2
- package/dist/discord/plan-forge-availability.js +22 -0
- package/dist/discord/plan-forge-availability.test.js +34 -0
- package/dist/discord/plan-manager.js +3 -2
- package/dist/discord/plan-manager.test.js +65 -0
- package/dist/discord/plan-parser.js +33 -0
- package/dist/discord/prompt-common.js +27 -0
- package/dist/discord/reaction-handler.js +43 -5
- package/dist/discord/reaction-handler.test.js +68 -1
- package/dist/discord/reaction-prompts.js +3 -15
- package/dist/discord/reaction-prompts.test.js +4 -4
- package/dist/discord/status-channel.js +16 -1
- package/dist/discord/status-channel.test.js +39 -2
- package/dist/discord/status-command.js +37 -5
- package/dist/discord/status-command.test.js +67 -1
- package/dist/discord/update-command.js +23 -0
- package/dist/discord/update-command.test.js +32 -0
- package/dist/discord/user-errors.test.js +2 -2
- package/dist/discord/verify-push.js +220 -0
- package/dist/discord/verify-push.test.js +260 -0
- package/dist/discord-followup.test.js +1 -1
- package/dist/discord.browser-command.integration.test.js +132 -0
- package/dist/discord.js +16 -0
- package/dist/health/config-doctor.js +142 -51
- package/dist/health/config-doctor.test.js +147 -5
- package/dist/health/credential-check.js +23 -4
- package/dist/health/credential-check.test.js +37 -15
- package/dist/index.js +328 -75
- package/dist/index.post-connect.js +21 -0
- package/dist/index.post-connect.test.js +62 -0
- package/dist/index.runtime.js +12 -0
- package/dist/index.runtime.test.js +35 -1
- package/dist/npm-managed.js +99 -0
- package/dist/npm-managed.test.js +48 -1
- package/dist/onboarding/onboarding-flow.js +7 -1
- package/dist/onboarding/onboarding-writer.js +10 -9
- package/dist/runtime/anthropic-rest.js +46 -3
- package/dist/runtime/anthropic-rest.test.js +2 -2
- package/dist/runtime/claude-code-cli.js +2 -1
- package/dist/runtime/cli-adapter.js +13 -4
- package/dist/runtime/cli-shared.js +65 -1
- package/dist/runtime/codex-cli.js +2 -1
- package/dist/runtime/long-running-process.js +9 -1
- package/dist/runtime/migration-hints.js +44 -0
- package/dist/runtime/migration-hints.test.js +40 -0
- package/dist/runtime/model-smoke-helpers.js +15 -15
- package/dist/runtime/model-smoke.test.js +68 -69
- package/dist/runtime/model-tiers.js +8 -1
- package/dist/runtime/model-tiers.test.js +38 -3
- package/dist/runtime/openai-compat.js +56 -0
- package/dist/runtime/openai-compat.test.js +97 -0
- package/dist/runtime/openrouter-smoke.test.js +206 -0
- package/dist/runtime/registry.test.js +4 -4
- package/dist/runtime/resolver.js +35 -0
- package/dist/runtime/resolver.test.js +71 -0
- package/dist/runtime/runtime-failure.js +0 -10
- package/dist/runtime/runtime-failure.test.js +7 -7
- package/dist/runtime/runtime-path-contract.js +147 -0
- package/dist/runtime/runtime-path-contract.test.js +133 -0
- package/dist/runtime/strategies/template-strategy.js +1 -1
- package/dist/runtime-overrides.js +16 -0
- package/dist/runtime-overrides.test.js +40 -9
- package/dist/service-control.js +18 -0
- package/dist/tasks/task-action-executor.test.js +6 -10
- package/dist/tasks/task-action-prompt.js +19 -58
- package/dist/vendor/canvas-runtime.js +2 -0
- package/dist/vendor/embedded-app-sdk.js +9981 -0
- package/dist/voice/voice-prompt-builder.js +1 -1
- package/dist/voice/voice-prompt-builder.test.js +2 -0
- package/docs/audit/claude-blank-machine-readiness.md +79 -0
- package/docs/audit/claude-npm-managed-path.md +110 -0
- package/docs/audit/codex-blank-machine-readiness.md +91 -0
- package/docs/audit/codex-npm-managed-path.md +125 -0
- package/docs/audit/gemini-support-boundary.md +55 -0
- package/docs/audit/openrouter-api-key-support-boundary.md +88 -0
- package/docs/audit/openrouter-parity-audit.md +102 -0
- package/docs/audit/provider-auth-1.0-matrix.md +71 -0
- package/docs/configuration.md +520 -0
- package/docs/discord-bot-setup.md +387 -0
- package/docs/official-docs.md +326 -1
- package/docs/runtime-switching.md +448 -0
- package/package.json +27 -6
- package/templates/canvas/README.md +11 -0
- package/templates/canvas/chart.html +74 -0
- package/templates/canvas/dashboard.html +34 -0
- package/templates/canvas/data-table.html +67 -0
- package/templates/canvas/diff-viewer.html +45 -0
- package/templates/canvas/form.html +86 -0
- package/templates/instructions/SYSTEM_DEFAULTS.md +25 -0
- package/templates/instructions/TOOLS.md +26 -3
- package/templates/instructions/canvas.md +32 -0
- package/dist/runtime/gemini-cli.js +0 -16
- package/dist/runtime/gemini-cli.test.js +0 -431
- package/dist/runtime/strategies/gemini-strategy.js +0 -63
- package/templates/instructions/SYSTEM_DEFAULTS.md.ledger.json +0 -5
package/.context/automations.md
CHANGED
|
@@ -85,3 +85,154 @@ creating forum threads.
|
|
|
85
85
|
| Gated actions | `allowedActions` for least-privilege |
|
|
86
86
|
|
|
87
87
|
See [docs/cron-patterns.md](../docs/cron-patterns.md) for full examples of each.
|
|
88
|
+
|
|
89
|
+
## Data Directory
|
|
90
|
+
|
|
91
|
+
Cron data lives in `data/cron/`:
|
|
92
|
+
|
|
93
|
+
| File/Dir | Contents |
|
|
94
|
+
|----------|----------|
|
|
95
|
+
| `cron-run-stats.json` | Per-job run statistics: run count, last run time/status, schedule, model, channel, projection state. Rewritten on every run completion. |
|
|
96
|
+
| `tag-map.json` | Maps tag names (e.g. `report`, `monitor`) to Discord forum tag snowflake IDs. Used for auto-tagging cron threads. |
|
|
97
|
+
| `locks/` | Per-job lock directories. Each active job gets `<sanitized-id>.<hash>.lock/meta.json`. Prevents overlap (concurrent execution of the same job). |
|
|
98
|
+
|
|
99
|
+
### Lock directory format
|
|
100
|
+
Each lock dir contains a `meta.json`:
|
|
101
|
+
```json
|
|
102
|
+
{"pid":12345,"token":"a1b2c3...","acquiredAt":"2026-03-24T15:00:00.009Z","startTime":45779798}
|
|
103
|
+
```
|
|
104
|
+
- `pid` — PID of the process holding the lock
|
|
105
|
+
- `token` — random token for safe release (prevents cross-process release)
|
|
106
|
+
- `startTime` — Linux `/proc/<pid>/stat` field 22 (jiffies); detects PID reuse
|
|
107
|
+
|
|
108
|
+
### Exact commands for data inspection
|
|
109
|
+
```bash
|
|
110
|
+
# View all job stats
|
|
111
|
+
jq . data/cron/cron-run-stats.json
|
|
112
|
+
|
|
113
|
+
# List jobs and their last run status
|
|
114
|
+
jq '.jobs | to_entries[] | {id: .key, status: .value.lastRunStatus, lastRun: .value.lastRunAt}' data/cron/cron-run-stats.json
|
|
115
|
+
|
|
116
|
+
# Check for stuck locks
|
|
117
|
+
ls -la data/cron/locks/
|
|
118
|
+
|
|
119
|
+
# Inspect a specific lock
|
|
120
|
+
cat data/cron/locks/*.lock/meta.json 2>/dev/null
|
|
121
|
+
|
|
122
|
+
# Check if a locked PID is still alive
|
|
123
|
+
for meta in data/cron/locks/*.lock/meta.json; do
|
|
124
|
+
pid=$(jq -r .pid "$meta" 2>/dev/null)
|
|
125
|
+
kill -0 "$pid" 2>/dev/null && echo "$meta: PID $pid alive" || echo "$meta: PID $pid STALE"
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
# View the tag map
|
|
129
|
+
jq . data/cron/tag-map.json
|
|
130
|
+
|
|
131
|
+
# Check run stats file size (large = many jobs or accumulating history)
|
|
132
|
+
ls -lh data/cron/cron-run-stats.json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Known Footguns
|
|
136
|
+
|
|
137
|
+
- **`<cron-state>` replaces, does not merge:** If a job's state is `{"cursor": "abc", "count": 5}` and the AI outputs `<cron-state>{"cursor": "def"}</cron-state>`, the `count` key is lost. The AI must echo back all keys it wants to keep.
|
|
138
|
+
- **Manual thread creation is ignored:** Jobs must be created via the `cronCreate` action. Manually creating a forum thread will not register a job — the thread will sit inert. Use `cronCreate` or ask the bot to create it.
|
|
139
|
+
- **Archiving ≠ deleting:** The `cronDelete` action archives the thread (reversible). The job stops, but history is preserved. Actual thread deletion is permanent and removes all messages.
|
|
140
|
+
- **Chain depth silently caps at 10:** If a chain exceeds 10 hops, execution stops with no visible error in the target channel. Check the bot logs for `chain depth limit reached`.
|
|
141
|
+
- **Webhook jobs require `DISCOCLAW_WEBHOOK_ENABLED=true`:** Defining a webhook-triggered job without enabling the webhook server means the job exists but can never fire. No warning is logged at startup.
|
|
142
|
+
- **Timezone defaults to system timezone:** If `DEFAULT_TIMEZONE` is unset and the server's system timezone is UTC, all cron schedules without explicit timezones run in UTC. This catches people who expect local time.
|
|
143
|
+
- **Cron config changes in `.env` require restart:** Changing `DISCOCLAW_CRON_ENABLED`, `DISCOCLAW_CRON_FORUM`, or `DEFAULT_TIMEZONE` in `.env` has no effect until the service is restarted (`systemctl --user restart discoclaw.service`). The cron subsystem reads config once at startup.
|
|
144
|
+
- **`dist/` must be rebuilt for cron code changes:** Cron executor, parser, and scheduler code lives in `src/cron/`. After modifying these files, `pnpm build` must be run and the service restarted — the systemd service runs `dist/index.js`, not `src/`.
|
|
145
|
+
|
|
146
|
+
## Common Failure Modes
|
|
147
|
+
|
|
148
|
+
### Cron job not firing on schedule
|
|
149
|
+
**Symptom:** Job was created and confirmed, schedule looks correct, but no output appears at the expected time.
|
|
150
|
+
**Cause (in order of likelihood):**
|
|
151
|
+
1. Thread is archived (job is paused).
|
|
152
|
+
2. Timezone mismatch — job runs in UTC but user expects local time.
|
|
153
|
+
3. Previous run is still active (overlap guard skipped this tick).
|
|
154
|
+
4. `DISCOCLAW_CRON_ENABLED` is `0` or `DISCOCLAW_CRON_FORUM` is unset.
|
|
155
|
+
5. Service is in `failed` state after crash loop — cron timers are not running.
|
|
156
|
+
**Recovery:**
|
|
157
|
+
```bash
|
|
158
|
+
# First: is the service even running?
|
|
159
|
+
systemctl --user status discoclaw.service
|
|
160
|
+
# If "failed (Result: start-limit-hit)":
|
|
161
|
+
systemctl --user reset-failed discoclaw.service
|
|
162
|
+
systemctl --user start discoclaw.service
|
|
163
|
+
|
|
164
|
+
# Check if the thread is archived (in Discord, archived threads are hidden by default)
|
|
165
|
+
# Use the cronList action to see all jobs and their states
|
|
166
|
+
|
|
167
|
+
# Check bot logs for skip reasons
|
|
168
|
+
journalctl --user -u discoclaw.service --since "1 hour ago" --no-pager | grep -i "cron\|skip\|overlap"
|
|
169
|
+
|
|
170
|
+
# Verify cron config
|
|
171
|
+
grep -E 'DISCOCLAW_CRON_ENABLED|DISCOCLAW_CRON_FORUM|DEFAULT_TIMEZONE' .env
|
|
172
|
+
|
|
173
|
+
# Verify timezone
|
|
174
|
+
timedatectl | grep "Time zone"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Cron job fires but output goes to wrong channel
|
|
178
|
+
**Symptom:** Job runs successfully but posts to the wrong Discord channel or to no channel.
|
|
179
|
+
**Cause:** The AI parsed the target channel incorrectly from the natural-language definition, or the channel ID in the parsed config is stale (channel was deleted/renamed).
|
|
180
|
+
**Recovery:**
|
|
181
|
+
```bash
|
|
182
|
+
# Use cronShow to inspect the parsed config
|
|
183
|
+
# Ask the bot: "show cron <job-name>"
|
|
184
|
+
# Check the targetChannel field
|
|
185
|
+
|
|
186
|
+
# Update by editing the starter message in the forum thread, then:
|
|
187
|
+
# Ask the bot: "trigger cron <job-name>" to test the new config
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### State corruption — job keeps repeating or skipping work
|
|
191
|
+
**Symptom:** A stateful polling job re-processes old items, or skips new ones.
|
|
192
|
+
**Cause:** The AI's `<cron-state>` output replaced state instead of merging, or the state hit the 4000-char injection cap and was truncated.
|
|
193
|
+
**Recovery:**
|
|
194
|
+
```bash
|
|
195
|
+
# Check current state via cronShow
|
|
196
|
+
# Reset state by asking the bot:
|
|
197
|
+
# "update cron <job-name> with state {}"
|
|
198
|
+
|
|
199
|
+
# Or reset to a specific cursor:
|
|
200
|
+
# "update cron <job-name> with state {\"cursor\": \"known-good-value\"}"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Cron job stuck — overlap guard never releases
|
|
204
|
+
**Symptom:** A job ran once and now never fires again. Logs show `Lock held by PID XXXXX` on every tick.
|
|
205
|
+
**Cause:** The previous execution crashed without releasing its lock in `data/cron/locks/`. The lock dir persists with a `meta.json` pointing to a dead (or reused) PID. Stale-lock detection usually catches this, but can fail if `/proc/<pid>/stat` is unreadable or the PID was reused by a long-lived process.
|
|
206
|
+
**Recovery:**
|
|
207
|
+
```bash
|
|
208
|
+
# List all lock dirs
|
|
209
|
+
ls -la data/cron/locks/
|
|
210
|
+
|
|
211
|
+
# Find the stuck lock (match the cron ID from the log message)
|
|
212
|
+
# Lock dirs are named <sanitized-id>.<hash>.lock
|
|
213
|
+
cat data/cron/locks/*.lock/meta.json 2>/dev/null
|
|
214
|
+
|
|
215
|
+
# Check if the PID is alive
|
|
216
|
+
kill -0 <pid> 2>/dev/null && echo "alive" || echo "stale"
|
|
217
|
+
|
|
218
|
+
# If stale: remove the lock dir, the next tick will acquire fresh
|
|
219
|
+
rm -rf data/cron/locks/<lock-dir-name>.lock
|
|
220
|
+
|
|
221
|
+
# If the PID is alive but belongs to a different process (PID reuse):
|
|
222
|
+
# compare the startTime in meta.json with /proc/<pid>/stat field 22
|
|
223
|
+
cat /proc/<pid>/stat | awk '{print $22}'
|
|
224
|
+
# If they differ, the lock is stale — safe to remove
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Chain cascade — downstream jobs keep firing
|
|
228
|
+
**Symptom:** A chained pipeline fires repeatedly or produces unexpected output.
|
|
229
|
+
**Cause:** Cycle in the chain graph (should be caught at write time, but can occur if jobs were edited after initial creation without re-validation).
|
|
230
|
+
**Recovery:**
|
|
231
|
+
```bash
|
|
232
|
+
# Check logs for chain-related entries
|
|
233
|
+
journalctl --user -u discoclaw.service --since "30 min ago" --no-pager | grep -i "chain"
|
|
234
|
+
|
|
235
|
+
# Break the cycle by removing the chain field from the looping job:
|
|
236
|
+
# "update cron <job-name> with chain: none"
|
|
237
|
+
# Then re-architect the chain graph
|
|
238
|
+
```
|
package/.context/bot-setup.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
## Quick reference
|
|
6
6
|
|
|
7
7
|
1. **Developer Portal** → create application → Bot → enable **Message Content Intent** → copy token to `.env` (`DISCORD_TOKEN`).
|
|
8
|
-
2. **
|
|
8
|
+
2. **Invite** — quickest: `https://discord.com/oauth2/authorize?client_id=CLIENT_ID&scope=bot&permissions=0` (replace `CLIENT_ID`). For a permanent link with pre-set permissions, use the **Installation page** → set Install Link to "Discord Provided Link" → Default Install Settings → add scope `bot` and pick permissions (see profiles in `docs/discord-bot-setup.md`). The `bot` scope is required — `applications.commands` alone won't add the bot to a server.
|
|
9
9
|
3. **Configure `.env`**:
|
|
10
10
|
- *Global install:* `discoclaw init` — wizard creates `.env` with `DISCORD_TOKEN`, `DISCORD_ALLOW_USER_IDS`, and `DISCORD_CHANNEL_IDS`.
|
|
11
11
|
- *From source:* `pnpm setup` for guided configuration, or copy `.env.example` → `.env` and set `DISCORD_TOKEN`, `DISCORD_ALLOW_USER_IDS` (fail-closed if empty), `DISCORD_CHANNEL_IDS` (recommended).
|
|
@@ -13,12 +13,275 @@
|
|
|
13
13
|
- *Global install:* `discoclaw install-daemon` to register the systemd service, then DM the bot to confirm it responds.
|
|
14
14
|
- *From source:* `pnpm dev`, DM the bot, post in allowed/disallowed channels.
|
|
15
15
|
|
|
16
|
+
## Exact Command Reference
|
|
17
|
+
|
|
18
|
+
### From source — setup and validation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Guided interactive setup (creates .env)
|
|
22
|
+
pnpm setup
|
|
23
|
+
|
|
24
|
+
# Manual .env creation (essentials only)
|
|
25
|
+
cp .env.example .env
|
|
26
|
+
|
|
27
|
+
# Preflight check — validates env, token format, snowflake IDs, config doctor
|
|
28
|
+
pnpm preflight
|
|
29
|
+
|
|
30
|
+
# Preflight for a fresh clone (ignores shell env, reads only checkout .env)
|
|
31
|
+
pnpm preflight:blank-machine
|
|
32
|
+
|
|
33
|
+
# Discord smoke test — verifies bot token and gateway connection
|
|
34
|
+
pnpm discord:smoke-test
|
|
35
|
+
|
|
36
|
+
# Discord smoke test with guild membership check
|
|
37
|
+
pnpm discord:smoke-test -- --guild-id 123456789012345678
|
|
38
|
+
|
|
39
|
+
# Claude runtime auth check
|
|
40
|
+
pnpm claude:auth-smoke
|
|
41
|
+
|
|
42
|
+
# Start the bot in dev mode
|
|
43
|
+
pnpm dev
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Global install — setup and validation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Interactive wizard — creates .env and workspace
|
|
50
|
+
discoclaw init
|
|
51
|
+
|
|
52
|
+
# Register as a user-level systemd service
|
|
53
|
+
discoclaw install-daemon
|
|
54
|
+
|
|
55
|
+
# Multi-instance: unique service name per instance
|
|
56
|
+
discoclaw install-daemon --service-name discoclaw-work
|
|
57
|
+
|
|
58
|
+
# Config/health check
|
|
59
|
+
discoclaw doctor
|
|
60
|
+
|
|
61
|
+
# Claude auth smoke test (npm-managed path)
|
|
62
|
+
discoclaw claude auth-smoke
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Discord smoke test arguments
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Print guild IDs the bot is in (useful for debugging guild-id mismatches)
|
|
69
|
+
pnpm discord:smoke-test -- --print-guilds
|
|
70
|
+
|
|
71
|
+
# Override timeout (default 12s)
|
|
72
|
+
DISCORD_SMOKE_TEST_TIMEOUT_MS=30000 pnpm discord:smoke-test
|
|
73
|
+
```
|
|
74
|
+
|
|
16
75
|
## Getting IDs
|
|
17
76
|
|
|
18
77
|
Discord client: Settings → Advanced → Developer Mode, then right-click a user/channel → Copy ID.
|
|
19
78
|
|
|
20
|
-
|
|
79
|
+
IDs are "snowflakes" — 17–20 digit numbers. The preflight check validates format:
|
|
80
|
+
```bash
|
|
81
|
+
# Preflight reports invalid snowflakes explicitly
|
|
82
|
+
pnpm preflight
|
|
83
|
+
# Example output: "DISCORD_ALLOW_USER_IDS contains invalid IDs: abc123"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Known Footguns
|
|
87
|
+
|
|
88
|
+
- **Message Content Intent is invisible when missing.** If you skip enabling it in Developer Portal → Bot → Privileged Gateway Intents, the bot connects, appears online, and `msg.content` is silently empty in guild channels. No error is logged — the bot just ignores every guild message. DMs still work (they don't require the intent). This is the #1 setup failure mode.
|
|
89
|
+
- **`applications.commands` scope is not `bot` scope.** Using `scope=applications.commands` in the invite URL registers slash commands but does not add the bot to the server. The bot cannot connect, read messages, or respond. You must include `scope=bot` (or both).
|
|
90
|
+
- **Copying Application ID instead of Bot Token.** The Developer Portal shows the Application ID prominently on General Information. The Bot Token is on the Bot page. They look similar (long alphanumeric strings) but are not interchangeable. If you copy the Application ID into `DISCORD_TOKEN`, the bot fails at login with "An invalid token was provided."
|
|
91
|
+
- **Token revealed once, then hidden.** After creating the bot, the token is shown once. If you navigate away without copying it, you must click "Reset Token" to generate a new one — the old one is gone. Resetting invalidates the previous token immediately.
|
|
92
|
+
- **Empty `DISCORD_ALLOW_USER_IDS` = silent rejection.** The bot starts, connects, appears online, but responds to nobody. No error is logged. This is fail-closed by design, but it's surprising on first setup.
|
|
93
|
+
- **`DISCORD_CHANNEL_IDS` restricts guild channels only.** DMs always work regardless of this setting. If you set channel IDs and then test in a channel not in the list, the bot silently ignores you. Test in a listed channel or via DM.
|
|
94
|
+
- **`DISCORD_REQUIRE_CHANNEL_CONTEXT=1` (default) blocks unlisted channels.** Even if the channel is in `DISCORD_CHANNEL_IDS`, the bot won't respond in a guild channel without a matching context file under `content/discord/channels/`. Enable `DISCORD_AUTO_INDEX_CHANNEL_CONTEXT=1` (default) to auto-create stubs, or run `pnpm sync:discord-context` to scaffold them.
|
|
95
|
+
- **Role hierarchy blocks moderation actions.** The bot can only manage roles below its own role in Server Settings → Roles. If the bot role is at the bottom, actions like role assignment, timeout, and kick will fail with "Missing Permissions" even if the permission bits are granted.
|
|
96
|
+
- **Private threads require explicit addition.** The bot must be added to private threads manually regardless of its channel permissions. Public threads are auto-joined when `DISCORD_AUTO_JOIN_THREADS=1`.
|
|
97
|
+
- **Editing `.env.example` instead of `.env`.** `.env.example` is tracked in git and has no runtime effect. Your actual config is in `.env` (gitignored). A common mistake — changes to `.env.example` appear to do nothing.
|
|
98
|
+
- **`discoclaw install-daemon` PATH divergence.** The systemd service uses `/usr/bin/node` and a fixed `PATH`. The daemon may not find the same CLI binaries (e.g., `claude`, `codex`) that your interactive shell finds. Verify service logs after daemon install.
|
|
99
|
+
- **Service stuck after repeated setup failures.** If the service crashes 3 times within 10 minutes during initial setup (bad token, missing env, etc.), systemd marks it as `failed` and refuses further starts. Run `systemctl --user reset-failed discoclaw.service` to clear the failure counter before retrying.
|
|
100
|
+
- **Stale `dist/` after setup changes.** If you checked out a different branch or changed source files during setup, `dist/` may contain old compiled code. Run `rm -rf dist && pnpm build` before `pnpm dev` or service start to ensure fresh output.
|
|
101
|
+
|
|
102
|
+
## Common Failure Modes
|
|
103
|
+
|
|
104
|
+
### Bot token rejected at login
|
|
105
|
+
**Symptom:** `pnpm dev` or the systemd service exits immediately. Logs show `Login failed: Error [TOKEN_INVALID]: An invalid token was provided.`
|
|
106
|
+
**Cause:** Wrong value in `DISCORD_TOKEN` — usually the Application ID was copied instead of the Bot Token, or the token was reset in the Developer Portal.
|
|
107
|
+
**Recovery:**
|
|
108
|
+
```bash
|
|
109
|
+
# Verify token is set and looks right (3 dot-separated base64url segments)
|
|
110
|
+
pnpm preflight
|
|
111
|
+
|
|
112
|
+
# If format is wrong: go to Developer Portal → Bot → Reset Token → copy new token
|
|
113
|
+
# Update .env:
|
|
114
|
+
# DISCORD_TOKEN=<new-token>
|
|
115
|
+
|
|
116
|
+
# Verify again
|
|
117
|
+
pnpm preflight
|
|
118
|
+
|
|
119
|
+
# Restart
|
|
120
|
+
pnpm dev
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Bot online but ignores all guild messages (Message Content Intent)
|
|
124
|
+
**Symptom:** Bot appears online in Discord. DMs work. Guild channel messages are completely ignored — no response, no error in logs.
|
|
125
|
+
**Cause:** Message Content Intent not enabled in the Developer Portal. Without it, `msg.content` is empty for guild messages, so the bot sees every message as blank and drops it.
|
|
126
|
+
**Recovery:**
|
|
127
|
+
1. Go to Discord Developer Portal → your application → **Bot** → **Privileged Gateway Intents**
|
|
128
|
+
2. Enable **Message Content Intent**
|
|
129
|
+
3. Save Changes
|
|
130
|
+
4. Restart the bot:
|
|
131
|
+
```bash
|
|
132
|
+
# From source
|
|
133
|
+
pnpm dev
|
|
134
|
+
|
|
135
|
+
# Or systemd
|
|
136
|
+
systemctl --user restart discoclaw.service
|
|
137
|
+
```
|
|
138
|
+
No code or `.env` change is needed — this is purely a Developer Portal toggle.
|
|
139
|
+
|
|
140
|
+
### Bot online but ignores all messages (empty allowlist)
|
|
141
|
+
**Symptom:** Bot appears online. No response to DMs or guild messages. Logs show no errors.
|
|
142
|
+
**Cause:** `DISCORD_ALLOW_USER_IDS` is empty, missing, or set to a wrong user ID.
|
|
143
|
+
**Recovery:**
|
|
144
|
+
```bash
|
|
145
|
+
# Check what's configured
|
|
146
|
+
grep DISCORD_ALLOW_USER_IDS .env
|
|
147
|
+
|
|
148
|
+
# If empty or wrong: get your Discord user ID
|
|
149
|
+
# (Discord → Settings → Advanced → Developer Mode → right-click yourself → Copy ID)
|
|
150
|
+
# Update .env:
|
|
151
|
+
# DISCORD_ALLOW_USER_IDS=123456789012345678
|
|
152
|
+
|
|
153
|
+
# Preflight validates the snowflake format
|
|
154
|
+
pnpm preflight
|
|
155
|
+
|
|
156
|
+
# Restart
|
|
157
|
+
pnpm dev
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Discord smoke test fails: "Used disallowed intents"
|
|
161
|
+
**Symptom:** `pnpm discord:smoke-test` exits with `Login failed: Error: Used disallowed intents`. Also appears in `journalctl` logs for the systemd service.
|
|
162
|
+
**Cause:** The bot requests `GatewayIntentBits.MessageContent` (a privileged intent) but the Developer Portal has it disabled.
|
|
163
|
+
**Recovery:** Same as "Bot online but ignores all guild messages" above — enable Message Content Intent in the Developer Portal. Then:
|
|
164
|
+
```bash
|
|
165
|
+
pnpm discord:smoke-test
|
|
166
|
+
# Expected: "Discord bot ready (guilds: N)"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Discord smoke test fails: "guild_not_in_cache"
|
|
170
|
+
**Symptom:** `pnpm discord:smoke-test -- --guild-id <ID>` prints `Discord bot ready, but it does not appear to be in guild <ID>`.
|
|
171
|
+
**Cause:** The bot is not in that guild, or the guild ID is wrong.
|
|
172
|
+
**Recovery:**
|
|
173
|
+
```bash
|
|
174
|
+
# List guilds the bot is actually in
|
|
175
|
+
pnpm discord:smoke-test -- --print-guilds
|
|
176
|
+
|
|
177
|
+
# If the guild is missing: re-invite the bot to that server
|
|
178
|
+
# Use the invite URL from step 2 of the quick reference above
|
|
179
|
+
|
|
180
|
+
# If the guild ID is wrong: copy the correct one
|
|
181
|
+
# (right-click server name → Copy Server ID)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Discord smoke test hangs then times out
|
|
185
|
+
**Symptom:** `pnpm discord:smoke-test` prints nothing for 12 seconds, then `Smoke test timed out after 12000ms`.
|
|
186
|
+
**Cause:** Network issue, firewall blocking Discord WebSocket, or the token is valid but the bot cannot establish a gateway connection.
|
|
187
|
+
**Recovery:**
|
|
188
|
+
```bash
|
|
189
|
+
# Increase timeout to rule out slow networks
|
|
190
|
+
DISCORD_SMOKE_TEST_TIMEOUT_MS=30000 pnpm discord:smoke-test
|
|
191
|
+
|
|
192
|
+
# If it still times out: check network/firewall
|
|
193
|
+
# Discord WebSocket connects to gateway.discord.gg on port 443
|
|
194
|
+
curl -s -o /dev/null -w "%{http_code}" https://discord.com/api/v10/gateway
|
|
195
|
+
# Expected: 200
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Preflight fails: "DISCORD_TOKEN format invalid"
|
|
199
|
+
**Symptom:** `pnpm preflight` reports `DISCORD_TOKEN format invalid: <reason>`.
|
|
200
|
+
**Cause:** Token is malformed — not three dot-separated base64url segments. Common when the Application ID was pasted, or extra whitespace/quotes were included.
|
|
201
|
+
**Recovery:**
|
|
202
|
+
```bash
|
|
203
|
+
# Check for accidental quotes or whitespace
|
|
204
|
+
grep DISCORD_TOKEN .env
|
|
205
|
+
# Must be: DISCORD_TOKEN=MTIz...abc (no quotes, no spaces)
|
|
206
|
+
|
|
207
|
+
# If the value looks wrong: regenerate in Developer Portal → Bot → Reset Token
|
|
208
|
+
# Paste the new token (no quotes around the value)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Preflight fails: "DISCORD_ALLOW_USER_IDS contains invalid IDs"
|
|
212
|
+
**Symptom:** `pnpm preflight` reports specific IDs that are not valid snowflakes.
|
|
213
|
+
**Cause:** Non-numeric characters, a username instead of a numeric ID, or copy-paste artifacts (e.g., `<@123>` instead of `123`).
|
|
214
|
+
**Recovery:**
|
|
215
|
+
```bash
|
|
216
|
+
# Discord snowflakes are 17-20 digit numbers
|
|
217
|
+
# Get the correct ID: Discord → right-click user → Copy ID
|
|
218
|
+
# IDs are comma or space separated in .env:
|
|
219
|
+
# DISCORD_ALLOW_USER_IDS=123456789012345678,987654321098765432
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Bot responds in DMs but not in guild channels
|
|
223
|
+
**Symptom:** DMs work. Guild channel messages are ignored. Message Content Intent is confirmed enabled.
|
|
224
|
+
**Cause:** `DISCORD_CHANNEL_IDS` is set but doesn't include the channel you're testing in, or `DISCORD_REQUIRE_CHANNEL_CONTEXT=1` and no context file exists for that channel.
|
|
225
|
+
**Recovery:**
|
|
226
|
+
```bash
|
|
227
|
+
# Check channel restrictions
|
|
228
|
+
grep DISCORD_CHANNEL_IDS .env
|
|
229
|
+
# If set: add the channel ID, or clear it to allow all channels
|
|
230
|
+
|
|
231
|
+
# Check channel context requirement
|
|
232
|
+
grep DISCORD_REQUIRE_CHANNEL_CONTEXT .env
|
|
233
|
+
# If 1: ensure a context file exists for the channel
|
|
234
|
+
|
|
235
|
+
# Auto-scaffold missing context files
|
|
236
|
+
pnpm sync:discord-context
|
|
237
|
+
|
|
238
|
+
# Or disable the requirement (not recommended for production)
|
|
239
|
+
# DISCORD_REQUIRE_CHANNEL_CONTEXT=0
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Multi-instance: second instance uses wrong service name
|
|
243
|
+
**Symptom:** `!restart` or `!update apply` targets the wrong systemd unit. Or `discoclaw install-daemon --service-name X` doesn't take effect.
|
|
244
|
+
**Cause:** `DISCOCLAW_SERVICE_NAME` in `.env` doesn't match the name passed to `install-daemon`.
|
|
245
|
+
**Recovery:**
|
|
246
|
+
```bash
|
|
247
|
+
# Check what's in .env
|
|
248
|
+
grep DISCOCLAW_SERVICE_NAME .env
|
|
249
|
+
|
|
250
|
+
# Re-run install-daemon (it replaces the line in-place, never duplicates)
|
|
251
|
+
discoclaw install-daemon --service-name discoclaw-work
|
|
252
|
+
|
|
253
|
+
# Verify
|
|
254
|
+
grep DISCOCLAW_SERVICE_NAME .env
|
|
255
|
+
# Expected: DISCOCLAW_SERVICE_NAME=discoclaw-work
|
|
256
|
+
|
|
257
|
+
# Each instance needs its own working directory with its own .env
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Validation Checklist (quick)
|
|
261
|
+
|
|
262
|
+
After setup, run through these in order. Each step should pass before moving on.
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# 1. Preflight — validates .env, token format, snowflake IDs
|
|
266
|
+
pnpm preflight # from source
|
|
267
|
+
discoclaw doctor # global install
|
|
268
|
+
|
|
269
|
+
# 2. Discord connection — verifies gateway login
|
|
270
|
+
pnpm discord:smoke-test # from source only
|
|
271
|
+
|
|
272
|
+
# 3. Guild membership (optional)
|
|
273
|
+
pnpm discord:smoke-test -- --guild-id <YOUR_SERVER_ID>
|
|
274
|
+
|
|
275
|
+
# 4. Runtime auth (Claude path)
|
|
276
|
+
pnpm claude:auth-smoke # from source
|
|
277
|
+
discoclaw claude auth-smoke # global install
|
|
278
|
+
|
|
279
|
+
# 5. Live test — start the bot and interact
|
|
280
|
+
pnpm dev # from source
|
|
281
|
+
systemctl --user start discoclaw.service # systemd
|
|
21
282
|
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
283
|
+
# Then in Discord:
|
|
284
|
+
# - DM the bot → should respond (if allowlisted)
|
|
285
|
+
# - Post in an allowlisted channel → should respond
|
|
286
|
+
# - Post in a non-allowlisted channel → should NOT respond
|
|
287
|
+
```
|
package/.context/dev.md
CHANGED
|
@@ -194,7 +194,8 @@ Two setup paths:
|
|
|
194
194
|
| `OPENAI_MODEL` | `gpt-4o` | Default model for the OpenAI adapter |
|
|
195
195
|
| `OPENROUTER_API_KEY` | *(empty)* | OpenRouter API key; required when `PRIMARY_RUNTIME=openrouter` |
|
|
196
196
|
| `OPENROUTER_BASE_URL` | *(empty — OpenRouter default)* | OpenRouter API base URL override |
|
|
197
|
-
| `OPENROUTER_MODEL` | `anthropic/claude-sonnet-4` | Default model for the OpenRouter adapter |
|
|
197
|
+
| `OPENROUTER_MODEL` | `anthropic/claude-sonnet-4.6` | Default model for the OpenRouter adapter |
|
|
198
|
+
| `OPENROUTER_PROVIDER_PREFERENCES` | *(empty)* | Optional JSON string forwarded as OpenRouter's request `provider` object |
|
|
198
199
|
| `GEMINI_BIN` | `gemini` | Path/name of the Gemini CLI binary |
|
|
199
200
|
| `GEMINI_MODEL` | `gemini-2.5-pro` | Default model for the Gemini adapter |
|
|
200
201
|
| `CODEX_BIN` | `codex` | Path/name of the Codex CLI binary |
|
|
@@ -288,7 +289,8 @@ This is especially useful for systemd, where env loading can differ from your sh
|
|
|
288
289
|
- **Bot not responding:** Check allowlist (`DISCORD_ALLOW_USER_IDS`), channel restrictions (`DISCORD_CHANNEL_IDS`), and channel context requirement (`DISCORD_REQUIRE_CHANNEL_CONTEXT`).
|
|
289
290
|
- **Claude CLI errors:** Look for `runtime` or `spawn` in logs. Use `CLAUDE_DEBUG_FILE` to capture full CLI output.
|
|
290
291
|
- **Timeout issues:** Look for `timeout` in logs. Adjust `RUNTIME_TIMEOUT_MS` if needed.
|
|
291
|
-
- **PID lock conflicts:** Look for `pidlock` in logs. See ops.md for stale lock handling.
|
|
292
|
+
- **PID lock conflicts:** Look for `pidlock` or `pid lock` in logs. The lock is a directory at `data/discoclaw.pid.lock/` with `meta.json` inside. See ops.md for stale lock handling.
|
|
293
|
+
- **`.env` not loaded:** If env vars appear unset, check that `.env` exists (not `.env.example`) and is in the project root. `pnpm dev` loads it via dotenv; `node dist/index.js` does not.
|
|
292
294
|
|
|
293
295
|
## Task Auto-Sync
|
|
294
296
|
|
|
@@ -340,6 +342,123 @@ SMOKE_TEST_TIERS=fast SMOKE_TEST_TIMEOUT_MS=120000 pnpm test
|
|
|
340
342
|
|
|
341
343
|
The suite uses your real `.env` — the same config that runs the bot is sufficient. No separate test credentials are needed.
|
|
342
344
|
|
|
345
|
+
## Known Footguns
|
|
346
|
+
|
|
347
|
+
- **`pnpm build` caches stale output:** TypeScript's `tsc` writes to `dist/` incrementally. If you rename or delete a source file, the old `.js` remains in `dist/` and may be imported at runtime. **Symptoms:** mysterious `Cannot find module` errors for files that exist in source, or runtime behavior that doesn't match the code. **Fix:** `rm -rf dist && pnpm build`. Do this after any branch switch, file rename, or when `dist/` behavior doesn't match `src/`.
|
|
348
|
+
- **`.env` not loaded in subshells:** `pnpm dev` loads `.env` via dotenv, but raw `node dist/index.js` does not. Always use `pnpm dev` or `pnpm start` for local runs.
|
|
349
|
+
- **`pnpm i` after branch switch:** Switching branches that change `package.json` or `pnpm-lock.yaml` can leave `node_modules` in a stale state. Run `pnpm i` after checkout if you see unexpected import errors.
|
|
350
|
+
- **Port conflicts with webhook server:** If `DISCOCLAW_WEBHOOK_ENABLED=1` and another process holds port `9400` (or your configured `DISCOCLAW_WEBHOOK_PORT`), the bot crashes on startup with `EADDRINUSE`. Check with `lsof -i :9400`.
|
|
351
|
+
- **Editing `.env.example` vs `.env`:** `.env.example` is tracked in git and has no effect at runtime. Your actual config is in `.env` (gitignored). Editing the wrong file is a common mistake. **How to tell:** `git diff` shows changes → you edited the tracked `.env.example`. `.env` changes never appear in `git diff`.
|
|
352
|
+
- **`.env` values with spaces or special characters:** Values with spaces must be quoted (`VAR="value with spaces"`). Values with `#` are truncated at the `#` (treated as inline comment by dotenv). Wrap in double quotes to include literal `#` characters.
|
|
353
|
+
- **PID lock contention during rapid restarts:** If you `pnpm dev`, Ctrl-C, and immediately `pnpm dev` again, the lock directory (`data/discoclaw.pid.lock/`) may still exist from the previous process. The 2-second grace period can cause `PID lock initializing` errors. Wait 2 seconds or manually `rm -rf data/discoclaw.pid.lock` if it persists.
|
|
354
|
+
|
|
355
|
+
## Common Failure Modes
|
|
356
|
+
|
|
357
|
+
### `pnpm build` fails with type errors
|
|
358
|
+
**Symptom:** `tsc` reports type errors in `src/`. Build exits non-zero.
|
|
359
|
+
**Recovery:**
|
|
360
|
+
```bash
|
|
361
|
+
# Check for stale dist artifacts
|
|
362
|
+
rm -rf dist
|
|
363
|
+
pnpm build
|
|
364
|
+
|
|
365
|
+
# If errors persist, check for missing deps
|
|
366
|
+
pnpm i
|
|
367
|
+
pnpm build
|
|
368
|
+
|
|
369
|
+
# For type errors in unchanged files, your deps may have updated types
|
|
370
|
+
# Check what changed:
|
|
371
|
+
git diff pnpm-lock.yaml
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### `pnpm dev` exits immediately with "Missing DISCORD_TOKEN"
|
|
375
|
+
**Symptom:** Process exits within 1 second, logs `Missing required env: DISCORD_TOKEN`.
|
|
376
|
+
**Cause:** `.env` file is missing, misnamed, or the variable is commented out.
|
|
377
|
+
**Recovery:**
|
|
378
|
+
```bash
|
|
379
|
+
# Verify .env exists and has the token
|
|
380
|
+
ls -la .env
|
|
381
|
+
grep DISCORD_TOKEN .env
|
|
382
|
+
|
|
383
|
+
# If missing, create from example
|
|
384
|
+
cp .env.example .env
|
|
385
|
+
# Then edit .env with your actual values
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### `pnpm dev` starts but Claude CLI invocations fail
|
|
389
|
+
**Symptom:** Bot responds to messages but replies with an error like "Runtime invocation failed" or "spawn claude ENOENT".
|
|
390
|
+
**Cause:** Claude CLI binary not found on `PATH`, or wrong binary name in `CLAUDE_BIN`.
|
|
391
|
+
**Recovery:**
|
|
392
|
+
```bash
|
|
393
|
+
# Verify the CLI is installed and reachable
|
|
394
|
+
which claude
|
|
395
|
+
claude --version
|
|
396
|
+
|
|
397
|
+
# If installed but not found, check CLAUDE_BIN in .env
|
|
398
|
+
grep CLAUDE_BIN .env
|
|
399
|
+
|
|
400
|
+
# If using a non-standard path
|
|
401
|
+
CLAUDE_BIN=/path/to/claude pnpm dev
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Tests fail with "Cannot find module" errors
|
|
405
|
+
**Symptom:** `pnpm test` crashes before tests run, with Node module resolution errors.
|
|
406
|
+
**Cause:** `dist/` is stale or `node_modules` is incomplete.
|
|
407
|
+
**Recovery:**
|
|
408
|
+
```bash
|
|
409
|
+
rm -rf dist
|
|
410
|
+
pnpm i
|
|
411
|
+
pnpm build
|
|
412
|
+
pnpm test
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### `dist/` contains ghost files from deleted/renamed source
|
|
416
|
+
**Symptom:** Runtime imports a module that no longer exists in `src/`, or old behavior persists after source changes. `pnpm build` succeeds (tsc only checks current source files — it doesn't clean up old output).
|
|
417
|
+
**Cause:** `tsc` incremental compilation never deletes output files. Renamed `src/foo.ts` → `src/bar.ts` leaves `dist/foo.js` behind.
|
|
418
|
+
**Recovery:**
|
|
419
|
+
```bash
|
|
420
|
+
# Clean and rebuild
|
|
421
|
+
rm -rf dist
|
|
422
|
+
pnpm build
|
|
423
|
+
|
|
424
|
+
# Verify no ghosts: dist/ should only contain files corresponding to src/
|
|
425
|
+
# Quick check — file count should roughly match
|
|
426
|
+
ls src/**/*.ts | wc -l
|
|
427
|
+
ls dist/**/*.js | wc -l
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### PID lock error on `pnpm dev` — "PID lock initializing" or "another instance is already running"
|
|
431
|
+
**Symptom:** `pnpm dev` exits immediately with `PID lock initializing (dir age: Xms)` or `Another discoclaw instance is already running (PID XXXXX)`.
|
|
432
|
+
**Cause:** Previous `pnpm dev` was killed (Ctrl-C / SIGKILL) before the lock was released, or another instance is genuinely running.
|
|
433
|
+
**Recovery:**
|
|
434
|
+
```bash
|
|
435
|
+
# Check if another instance is actually running
|
|
436
|
+
cat data/discoclaw.pid.lock/meta.json 2>/dev/null
|
|
437
|
+
# If it shows a PID, check if alive:
|
|
438
|
+
kill -0 $(jq -r .pid data/discoclaw.pid.lock/meta.json) 2>/dev/null && echo "alive" || echo "stale"
|
|
439
|
+
|
|
440
|
+
# If stale or "initializing" error: remove and retry
|
|
441
|
+
rm -rf data/discoclaw.pid.lock
|
|
442
|
+
pnpm dev
|
|
443
|
+
|
|
444
|
+
# If alive: stop the other instance first, or use a different terminal
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### `pnpm sync:discord-context` fails
|
|
448
|
+
**Symptom:** Command exits with an error about missing `DISCORD.md` or content directory.
|
|
449
|
+
**Cause:** `DISCOCLAW_CONTENT_DIR` or `DISCOCLAW_DATA_DIR` not set, or the content directory doesn't exist yet.
|
|
450
|
+
**Recovery:**
|
|
451
|
+
```bash
|
|
452
|
+
# Check which content dir is configured
|
|
453
|
+
grep -E 'DISCOCLAW_CONTENT_DIR|DISCOCLAW_DATA_DIR' .env
|
|
454
|
+
|
|
455
|
+
# Create the directory structure if missing
|
|
456
|
+
mkdir -p data/content/discord/channels
|
|
457
|
+
|
|
458
|
+
# Re-run
|
|
459
|
+
pnpm sync:discord-context
|
|
460
|
+
```
|
|
461
|
+
|
|
343
462
|
## Notes
|
|
344
463
|
- Runtime invocation defaults are configurable via env (`RUNTIME_MODEL`, `RUNTIME_TOOLS`, `RUNTIME_TIMEOUT_MS`).
|
|
345
464
|
- If `pnpm dev` fails with "Missing DISCORD_TOKEN", your `.env` isn't loaded or the var is unset.
|