Native binding caused install pain on every new Node major (no prebuilts +
node-gyp needs VS+Windows SDK to fall back). For this project's workload
(a few ops/day, no advanced SQLite features) better-sqlite3's perf edge is
irrelevant — node:sqlite's bundled, ABI-stable sync API is the better fit.
- db.ts: DatabaseSync, db.exec("PRAGMA …"), explicit BEGIN/COMMIT helper to
replace db.transaction(); row casts go through unknown because node:sqlite
returns Record<string, SQLOutputValue>.
- package.json: drop better-sqlite3 + @types/better-sqlite3, bump
engines.node to >=24, vitest 2 → 4 (2.x couldn't resolve `node:sqlite`).
- mailbox-doctor: add Step 1 that enforces Node ≥24 with a concrete fix
message, renumbers downstream steps.
Node 1.2.0 → 1.3.0. 35 transitive packages removed from the lockfile.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.7 KiB
description, allowed-tools
| description | allowed-tools |
|---|---|
| Diagnose and auto-fix the Claude-Mailbox setup (Node version, binary install, port-conflict detection, daemon autostart, smoke test, optional base-prefix). | Bash, Read, Edit, Write |
You are running the Claude-Mailbox doctor. Walk through these checks in order. After each step, print a one-line ✓ / ✗ with the action you took. End with a summary block.
Use Bash only for claude-mailbox subcommands, npm, node, where/which, and HTTP probes. Use Read/Edit/Write for .claude/settings.json and mailbox.json. Never run sudo automatically — if elevation is needed, stop and ask.
Step 1 — Node.js version
Run: node --version
claude-mailbox uses Node's built-in node:sqlite and therefore requires Node 24 or newer. Parse the major version from the output.
- Major ≥ 24 → ✓ record the version, continue.
- Major == 22 or 23 → ✗ Stop.
node:sqliteis experimental on these and requires--experimental-sqlite. Print:Found Node
<X.Y.Z>. claude-mailbox needs Node 24 LTS or newer. Install vianvm install 24 && nvm use 24(ornvs/winget install OpenJS.NodeJS.LTSon Windows), then re-run the doctor. - Major < 22 → ✗ Stop with the same message; this Node is end-of-life.
- Major ≥ 26 with
better-sqlite3still installed globally from a previous version → just note: "Node<X.Y.Z>is fine for the current claude-mailbox (no native deps); ignore any oldbetter-sqlite3build warnings from a prior install."
If node --version itself fails (command not found), stop and tell the user to install Node 24+ first.
Step 2 — daemon binary on PATH
Run: claude-mailbox --version
-
Exit 0 → ✓ record the version. Continue.
-
Command not found → binary missing. Install path:
Platform Command Windows npm install -g @kuns/claude-mailbox(no admin)macOS / Linux npm install -g @kuns/claude-mailbox(may fail with EACCES — never run sudo automatically; ask the user)Prerequisite:
npm config get @kuns:registrymust point athttps://git.kuns.dev/api/packages/releases/npm/. If not:npm config set @kuns:registry=https://git.kuns.dev/api/packages/releases/npm/After install, re-run
claude-mailbox --version. If it still fails, stop and report.
Step 3 — port-conflict check (before autostart!)
Default port is 37849. Probe whether anything is already on it:
curl -sf http://127.0.0.1:37849/health
- Returns a JSON body with
"status":"ok"and aversionfield that matchesclaude-mailbox --version→ it's already our daemon, ✓ skip to Step 5. - Returns 200 with
"status":"ok"but a differentversion→ it's an older claude-mailbox; treat as running, ✓. - Returns non-200, non-JSON, or any other foreign response → port conflict. Some other process owns 37849.
- Connection refused → port is free, ✓ continue to Step 4.
If port conflict detected:
- Tell the user which process holds the port (Windows:
Get-NetTCPConnection -LocalPort 37849 | Select-Object OwningProcess, thenGet-Process -Id <pid>; macOS/Linux:lsof -i :37849). - Pick a free port. Default suggestion: 47900. Verify it's free:
curl -sf http://127.0.0.1:47900/healthshould fail with connection refused. - Read
~/.claude-mailbox/mailbox.json(create empty{}if missing) and merge{"port": <chosen>}. Write back. - Also write the override into
.claude/settings.jsonenv so the plugin's hooks find the right URL:Merge into existing env, preserving other keys."env": { "CLAUDE_MAILBOX_URL": "http://127.0.0.1:<chosen>" } - Mark
restart_needed = true.
Step 4 — daemon autostart and running state
Run: claude-mailbox status
Running→ ✓ continue.Stopped→claude-mailbox start, re-check.NotInstalled→claude-mailbox install-autostart, thenclaude-mailbox start, re-check.
Behavior on install-autostart: The CLI tries a Scheduled Task first (schtasks /RL LIMITED, no admin). If Windows Group Policy returns "Access is denied", it falls back transparently to an HKCU\Software\Microsoft\Windows\CurrentVersion\Run registry entry plus a hidden node serve process — same per-user persistence, no admin needed. The chosen mechanism is recorded in ~/.claude-mailbox/autostart-mode and respected by status/start/stop/uninstall-autostart.
If install-autostart still fails after both attempts (very rare — would mean both schtasks and reg add are blocked), stop and report what status and start printed.
Step 5 — health probe
Hit http://127.0.0.1:<port>/health (use the configured port, not necessarily 37849). Expect a JSON body with "status":"ok" AND a version matching claude-mailbox --version. If unreachable or version mismatch, stop and report.
Step 6 — mailbox identity (base prefix)
No prompt by default. Each Claude Code session gets a unique mailbox name auto-derived from its session_id (e.g., claude-a8b3c1d2).
Read .claude/settings.json and look for env.CLAUDE_MAILBOX_NAME.
- If set → ✓ "Mailbox prefix is
<X>." (real name will be<X>-<short_session_id>). - If unset → ✓ "Mailbox name will be auto-derived (
claude-<short_session_id>)."
Ask once: "Want to flavor your mailbox names with a memorable prefix (e.g., backend, frontend)? (yes / no / <name>)"
On yes/explicit name: merge env.CLAUDE_MAILBOX_NAME = <name> into .claude/settings.json, preserving other keys. Mark restart_needed = true.
Step 7 — smoke test
Use two ephemeral names — we don't need the real session name here:
claude-mailbox send --from doctor-probe-a --to doctor-probe-b --body "ping from doctor"
claude-mailbox check --name doctor-probe-b
(If the port was changed in Step 3, pass --url http://127.0.0.1:<port> to both.)
The check output must be a JSON array with one message: from: doctor-probe-a, body matches. ✓ on success, ✗ otherwise.
Step 8 — summary
Claude-Mailbox doctor
node: <version>
binary: <version>
daemon: Running (port: <port>, what you did if anything)
health: ok
port conflict: none | resolved (moved from 37849 to <port>)
base prefix: <name from settings, or "auto-derived (anonymous)">
smoke test: passed | failed
restart hint: yes if restart_needed, otherwise no
End with one of:
- All ✓ and no restart needed → "Setup is healthy. Your mailbox name this session will be revealed by the SessionStart hook on next session start."
- All ✓ and restart needed → "Restart Claude Code (or open a new session) so the SessionStart hook picks up the new env values."
- Anything ✗ → "Setup incomplete: ."