feat(session): force-kill a stuck session (engine forceDestroy + POST /sessions/:id/force-kill)#352
Closed
rmyndharis wants to merge 1 commit into
Closed
feat(session): force-kill a stuck session (engine forceDestroy + POST /sessions/:id/force-kill)#352rmyndharis wants to merge 1 commit into
rmyndharis wants to merge 1 commit into
Conversation
… /sessions/:id/force-kill) Adds an engine-neutral forceDestroy() to IWhatsAppEngine and a POST /sessions/:id/force-kill route (OPERATOR) to recover a session whose engine is wedged and won't respond to a normal stop()/delete(). - whatsapp-web.js: SIGKILLs THIS client's own Chromium process directly (client.pupBrowser.process()), never a process-wide pkill that could take down other sessions, then best-effort client.destroy(). - Baileys: ends its own socket (no separate browser process). - session.forceKill() mirrors stop()'s lifecycle (stop-mark + cancel-reconnect + time-bounded, isolated teardown + Map reconciliation), leaving the session DISCONNECTED and restartable. Backs the dashboard Kill Stuck button (#351), whose backend dependency was missing from main.
This was referenced Jun 19, 2026
Merged
rmyndharis
added a commit
that referenced
this pull request
Jun 19, 2026
* fix(webhook): deliver session lifecycle events and key webhook hardening (#335) * fix(security): pin outbound webhook and media fetches to validated IP (#338) * fix(plugins): persist plugin enable/config and restore (#339) * fix(message): persist bulk-sent messages, sanitize SSRF (#340) * fix(engine): return the real id for forwarded messages (#341) * fix(security): harden outbound requests, IPv6 SSRF (#344) * fix(security): secret-file perms, key pepper, allowedIps (#345) * fix(storage): bound tar imports, contain storage keys (#346) * fix(session): reconcile late acks, serialize reactions (#348) * fix(contract): webhook timeout, bounded shutdown, 501 (#350) * feat(session): force-kill a stuck session (#352) * merge #343 * merge #351 * chore(release): v0.4.3 — CHANGELOG, version bump (package.json/dashboard/swagger), README + docs
Owner
Author
|
Shipped in v0.4.3 (integrated via the release PR #354 and tagged |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backend for the "Kill Stuck" dashboard button (#351). #351's button calls
POST /api/sessions/:id/force-kill, but that endpoint did not exist onmain(the original backend PR, #316/#329, was closed with an empty branch). This adds it the engine-neutral way.Why not
pkill chromiumThe earlier attempt used a process-level
pkill, which is wrong for an engine-abstracted layer: it's Puppeteer-specific (meaningless for Baileys) and would kill other sessions' (or the system's) Chromium, not just the stuck one. Instead this puts the responsibility on the engine — each adapter kills only its own resources.What this adds
IWhatsAppEngine.forceDestroy()— force-kill the engine's own resources, then best-effort graceful teardown.client.pupBrowser.process(), then best-effortclient.destroy(). A missing process handle or a hungdestroy()can't prevent teardown.forceDestroy()is just adestroy().SessionService.forceKill(id)— mirrorsstop()'s lifecycle: sets the stop-mark (blocks an in-flight reconnect from resurrecting the session it just killed; a laterstart()clears it), cancels pending reconnects, and runs the time-bounded, isolated teardown (teardownEngineSafely, 10s cap) so a still-wedged engine can't hang the call. The Map is reconciled and the session is leftDISCONNECTEDand restartable.POST /sessions/:id/force-kill(OPERATOR, mirrorsstop) → 200 with the session, 404 if unknown. Audited assession_force_killed.No migration (the audit-action column is a free
varchar), no new dependency, no change to existing routes.Tests
forceDestroy: SIGKILLs only its own browser process then destroys; still completes when the process handle is gone anddestroy()rejects; no-op with no client.forceKill: force-destroys + reconciles the Map + keeps the stop-mark (so a late reconnect can't revive it); completes even whenforceDestroy()rejects; 404 for an unknown session.Full backend 734/734; dashboard build + lint clean.
Merge note
Merge this before #351 so the dashboard button has a working endpoint. The two together close the stuck-session-recovery gap.