Windows: Channels (Telegram plugin) unstable after reboot — zombie processes, port conflicts, exit code -1

Resolved 💬 3 comments Opened Apr 1, 2026 by zackchiutw Closed Apr 5, 2026

Summary

Running Claude Code with --channels plugin:telegram@claude-plugins-official on Windows as an always-on service (via Task Scheduler) is extremely unreliable. After every system reboot, users must manually fight zombie processes and port conflicts before the channel can resume. Even when the wrapper script handles cleanup, claude.exe itself frequently crashes with exit code -1 with no useful diagnostics.

Environment

  • OS: Windows 11 Pro (10.0.26200)
  • Claude Code: Latest (installed via ~/.local/bin/claude.exe)
  • Channel: plugin:telegram@claude-plugins-official
  • Permission mode: bypassPermissions
  • Launch method: Windows Task Scheduler → VBS silent launcher → CMD wrapper script
  • Upstream dependency: OpenClaw Gateway on port 25525 (TCP)

Problems observed

1. claude.exe exits with code -1 (no diagnostics)

After running for 1-2 hours, claude.exe silently exits with code -1. No crash dump, no error message, no useful log output. This happens repeatedly:

[2026-04-02 04:22:55] Starting claude.exe --channels plugin:telegram@claude-plugins-official
[2026-04-02 05:55:59] claude.exe exited with code -1
[2026-04-02 05:56:47] Starting claude.exe (restart attempt)
[2026-04-02 06:14:56] claude.exe exited with code -1

Earlier logs (3/27) show the same pattern, sometimes with triple-duplicate crash entries suggesting a race condition in the exit handler:

[2026-03-27 05:35:04] claude.exe exited with code -1
[2026-03-27 05:35:06] claude.exe exited with code -1
[2026-03-27 05:35:06] claude.exe exited with code -1

2. Zombie child processes (bun.exe) not cleaned up on exit

When claude.exe exits (normally or abnormally), the spawned bun.exe process running the Telegram MCP server (telegram/server.ts) is not terminated. This causes:

  • Port conflicts: The next claude.exe instance can't bind the same port
  • getUpdates race condition: Multiple Telegram pollers fight over the same bot token
  • Accumulated zombies: After a few crash-restart cycles, multiple bun.exe instances pile up

We had to build our own cleanup routine:

Get-CimInstance Win32_Process -Filter "Name='bun.exe'" |
  Where-Object { $_.CommandLine -match 'telegram.*server\.ts' } |
  ForEach-Object { Stop-Process -Id $_.ProcessId -Force }

3. No built-in single-instance guard

Running channels via Task Scheduler (triggered on logon) can spawn duplicate claude.exe --channels instances. Claude Code has no mechanism to detect an existing channel session and either reuse it or cleanly replace it.

We built a 230-line CMD wrapper (v4.1) with PID-based locking, WebSocket health checks, exponential backoff, and zombie cleanup — all of which should arguably be handled by Claude Code itself.

4. No channel health monitoring

There's no way to query whether a running channel is healthy (connected to upstream, processing messages). Our wrapper has to check for active WebSocket connections to the gateway port via Get-NetTCPConnection as a proxy for health — a fragile heuristic.

What we've built to work around this

Our wrapper script (claude-channel-start.cmd v4.1) implements:

  • PID-based lock file with write-then-verify (race condition prevention)
  • Gateway TCP probe with 5-minute timeout (30 retries × 10s)
  • WebSocket health check to distinguish "alive but stuck" from "alive and working"
  • Zombie bun.exe cleanup before start, after exit, and before restart
  • Exponential backoff: 15s → 30s → 60s → 120s → 240s (capped at 300s)
  • Failure limit (5 consecutive crashes → stop and alert)
  • Log rotation at 100KB

This works, but it's 230 lines of CMD scripting to paper over gaps that should be handled by Claude Code's process lifecycle.

Log timeline showing the evolution of pain

| Date | Version | Issue |
|------|---------|-------|
| 3/22 | v2.0 | Duplicate startups, no guard |
| 3/27 | v2.0 | Race condition: triple-duplicate crash logs, triple-duplicate restarts |
| 3/27 | v3.x | Process-count guard broken — stuck in "Aborted" loop for 8+ hours |
| 3/29 | v4.0 | PID lock fixed duplicates, but exit code -1 crashes continue |
| 4/02 | v4.1 | Same exit code -1 pattern, now with proper backoff |

Feature requests

  1. Graceful child process cleanup: When claude.exe exits, kill all spawned MCP server subprocesses (bun, node, etc.)
  2. Built-in single-instance guard: --channels mode should detect and handle existing instances (lock file, named pipe, etc.)
  3. Health endpoint: Expose a local HTTP or named-pipe health check for channel status (connected, message count, uptime)
  4. Structured exit codes: Differentiate between "plugin failed to load", "auth error", "network timeout", "OOM", etc. instead of blanket -1
  5. Windows service/daemon documentation: Official guidance for running channels as always-on services on Windows (Task Scheduler, NSSM, etc.)
  6. --restart-on-failure flag: Built-in auto-restart with configurable backoff, so users don't need wrapper scripts

Reproduction

claude.exe --channels plugin:telegram@claude-plugins-official --permission-mode bypassPermissions

Run this via Task Scheduler on Windows. Reboot. Observe:

  • Zombie bun.exe from previous session still holding resources
  • New claude.exe may fail silently or conflict with zombies
  • Exit code -1 after 1-2 hours of operation with no diagnostic output

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗