Skip to content

feat(cli): add threatcrush restart - #167

Merged
ralyodio merged 1 commit into
masterfrom
worktree-daemon-restart
Aug 30, 2026
Merged

feat(cli): add threatcrush restart#167
ralyodio merged 1 commit into
masterfrom
worktree-daemon-restart

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

start and stop already existed. The documented way to reload the daemon was threatcrush stop && threatcrush start, so this adds restart as a first-class command and points the module-reload hint, the SDK README, PRD.md and the web docs page at it.

Two correctness fixes fall out of doing it properly, both in the stop path:

daemonStop misread EPERM as "the process is gone". It used a raw process.kill(pid, 0) and treated the throw as death, but EPERM means the opposite: the process is alive and we may not signal it. A non-root client stopping a root-owned daemon would print ✓ threatcrushd stopped while the daemon kept running, then delete a pidfile that was still valid. It now uses isProcessAlive from pidfile.ts, which already handles EPERM correctly, keeps the pidfile when the daemon survives, and returns a boolean so restart can refuse to start a second daemon over a live socket.

A SIGKILLed daemon leaves its socket behind. daemonStart's readiness check is existsSync(socket), so a leftover file makes it report a green checkmark for a daemon that never bound. releaseStaleSocket waits for the file to go and unlinks it if it doesn't.

Verification

Built and exercised against a live daemon on Linux, user mode:

  • restart while running — old pid gone, new pid serving IPC with all five modules up
  • restart from cold — reports no running daemon, then starts
  • stop — clean shutdown, socket unlinked
  • restart over a socket left by kill -9 — new daemon genuinely bound, confirmed by status returning its real pid and uptime rather than the stale socket

tsc --noEmit clean, 38/38 vitest passing, pre-commit hook (CLI tsup build + next build) green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GF8cq44uC2nDHjrRNn6mWf

`start` and `stop` already existed; the documented way to reload the
daemon was `threatcrush stop && threatcrush start`. Add `restart` as a
first-class command and point the module-reload hint, the SDK README and
the docs page at it.

Two correctness fixes fall out of doing it properly:

- `daemonStop` read `process.kill(pid, 0)` throwing as "the process is
  gone", but EPERM means the opposite - it's alive and we may not signal
  it. A non-root client stopping a root-owned daemon would report
  success while the daemon kept running. It now uses `isProcessAlive`
  (which already handles EPERM), keeps the pidfile when the daemon
  survives, and returns whether the daemon is actually down so `restart`
  can refuse to start a second one over a live socket.

- A SIGKILLed daemon leaves its socket file behind, and `daemonStart`
  treats socket existence as "it came up". Without clearing it first,
  restart would print a green checkmark for a daemon that never bound.
  `releaseStaleSocket` waits for the file to go and unlinks it if not.

Verified end to end against a live daemon: restart while running (old pid
gone, new pid serving IPC with all five modules up), restart from cold,
stop, and restart over a socket left by `kill -9`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF8cq44uC2nDHjrRNn6mWf
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

12 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 6 | LOW: 5

Severity Rule Location
HIGH secret-aws-access-key prd/0003-detect-hardcoded-secrets-before-they-are-committed-or-served.md:126
MEDIUM js-open-redirect apps/web/src/app/auth/login/page.tsx:50
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:96
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:100
MEDIUM js-open-redirect apps/web/src/components/funding/FundingClient.tsx:97
MEDIUM js-unescaped-html-sink apps/web/src/components/GuideReader.tsx:265
MEDIUM js-uninitialized-buffer packages/scan/src/node-rules.ts:456
LOW secret-generic-credential PRD.md:269
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:121
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:122
LOW sh-remote-script-execution scripts/smoke-test.sh:47
LOW secret-aws-access-key scripts/smoke-test.sh:112

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 32df623 into master Aug 30, 2026
10 of 11 checks passed
@ralyodio
ralyodio deleted the worktree-daemon-restart branch August 30, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant