claude-code - 💡(How to fix) Fix enabledPlugins conflates dispatch gate and autoloader, causing plugin SIGTERM fratricide between concurrent sessions [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#49610Fetched 2026-04-17 08:36:18
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
labeled ×4commented ×1

enabledPlugins in ~/.claude/settings.json conflates two independent functions:

  1. Dispatch gate — whether --channels routes messages to a plugin
  2. Autoloader — whether every new claude process spawns the plugin at startup

This creates an unresolvable catch-22: there is no configuration state that enables --channels dispatch WITHOUT causing every child claude process to auto-spawn the plugin. When a child process spawns a second plugin instance, the plugin's kill-stale-pid startup guard SIGTERMs the parent's plugin instance ("fratricide"), leaving the parent session with a dead channel.

This affects any workflow that runs multiple concurrent claude sessions on the same machine (e.g., multiple terminal tabs, automation scripts spawning subprocesses, etc.).

Root Cause

enabledPlugins in ~/.claude/settings.json conflates two independent functions:

  1. Dispatch gate — whether --channels routes messages to a plugin
  2. Autoloader — whether every new claude process spawns the plugin at startup

This creates an unresolvable catch-22: there is no configuration state that enables --channels dispatch WITHOUT causing every child claude process to auto-spawn the plugin. When a child process spawns a second plugin instance, the plugin's kill-stale-pid startup guard SIGTERMs the parent's plugin instance ("fratricide"), leaving the parent session with a dead channel.

This affects any workflow that runs multiple concurrent claude sessions on the same machine (e.g., multiple terminal tabs, automation scripts spawning subprocesses, etc.).

Fix Action

Fix / Workaround

  1. Dispatch gate — whether --channels routes messages to a plugin
  2. Autoloader — whether every new claude process spawns the plugin at startup

This creates an unresolvable catch-22: there is no configuration state that enables --channels dispatch WITHOUT causing every child claude process to auto-spawn the plugin. When a child process spawns a second plugin instance, the plugin's kill-stale-pid startup guard SIGTERMs the parent's plugin instance ("fratricide"), leaving the parent session with a dead channel.

  • enabledPlugins (or a new setting) should control whether --channels dispatch is authorized, separately from whether the plugin is auto-loaded on every session.
  • OR a flag like --no-plugins should exist to prevent child processes from auto-loading plugins (note: --bare exists and prevents plugin loading, but it also disables hooks, CLAUDE.md, and auto-memory, making it unsuitable for real sessions).

Code Example

{ "enabledPlugins": { "telegram@claude-plugins-official": true } }
RAW_BUFFERClick to expand / collapse

Summary

enabledPlugins in ~/.claude/settings.json conflates two independent functions:

  1. Dispatch gate — whether --channels routes messages to a plugin
  2. Autoloader — whether every new claude process spawns the plugin at startup

This creates an unresolvable catch-22: there is no configuration state that enables --channels dispatch WITHOUT causing every child claude process to auto-spawn the plugin. When a child process spawns a second plugin instance, the plugin's kill-stale-pid startup guard SIGTERMs the parent's plugin instance ("fratricide"), leaving the parent session with a dead channel.

This affects any workflow that runs multiple concurrent claude sessions on the same machine (e.g., multiple terminal tabs, automation scripts spawning subprocesses, etc.).

Reproduction Steps

  1. Set ~/.claude/settings.json to include:
    { "enabledPlugins": { "telegram@claude-plugins-official": true } }
  2. Start a claude session with --channels plugin:telegram@claude-plugins-official. Verify the plugin starts and the Telegram bot is polling (check ~/.claude/channels/telegram/bot.pid).
  3. In a separate terminal, start any claude process — even a simple claude --print "hello" with no --channels flag.
  4. Observe: the second claude process reads enabledPlugins, auto-loads the Telegram plugin, the new plugin reads bot.pid, finds the first session's plugin PID, sends SIGTERM to it, and takes over the poll slot.
  5. The first session's plugin is now dead. The first session loses all inbound channel functionality.
  6. When the second claude process exits, its plugin also dies (stdin EOF), leaving zero active pollers.

Expected Behavior

Starting a second claude process (without --channels) should NOT interfere with an existing session's plugin. Ideally:

  • enabledPlugins (or a new setting) should control whether --channels dispatch is authorized, separately from whether the plugin is auto-loaded on every session.
  • OR a flag like --no-plugins should exist to prevent child processes from auto-loading plugins (note: --bare exists and prevents plugin loading, but it also disables hooks, CLAUDE.md, and auto-memory, making it unsuitable for real sessions).

Actual Behavior

enabledPlugins Truth Table (empirically verified on v2.1.110)

enabledPlugins value--channels dispatchChild session (no --channels)
{}BLOCKED — plugin not startedNo plugin spawned
{"telegram@...": true}WORKSSPAWNS PLUGIN → SIGTERM fratricide
key absentBLOCKEDNo plugin spawned
{"telegram@...": false}BLOCKEDNo plugin spawned

No state satisfies "dispatch works AND children don't spawn plugins."

Kill-stale-pid mechanism (plugin source server.ts, lines ~58-68)

On startup, the plugin:

  1. Reads ~/.claude/channels/telegram/bot.pid
  2. If a PID is found and the process is alive, sends SIGTERM to it
  3. Writes its own PID to bot.pid
  4. Begins polling Telegram

This is a "last writer wins" singleton guard. It works correctly when a single session restarts its own plugin, but becomes destructive when concurrent sessions each spawn their own plugin instance. The SIGTERM is unconditional — it does not check whether the existing plugin belongs to a different session.

Secondary Bug: Orphan Watchdog Broken on macOS/Bun

The plugin includes an orphan watchdog (lines ~657-664 of server.ts) that checks process.ppid !== bootPpid to detect when the parent claude process has died. However, process.ppid in Bun (tested on 1.3.11, macOS) is a startup snapshot that never updates after the real parent dies.

Evidence: after killing the parent process, process.ppid continued to report the dead parent's PID at t=1s, 3s, and 8s, while the kernel-level ppid had already changed to 1 (init/launchd). The watchdog conditions (ppid !== bootPpid, stdin.destroyed, stdin.readableEnded) did not fire within 10 seconds of parent death.

This means orphaned plugin processes linger indefinitely until the next session's fratricide guard kills them, or until the machine is restarted.

This is independent from the enabledPlugins conflation bug — either bug alone would cause reliability issues; together they create a situation where the plugin is essentially unusable with concurrent sessions.

Suggested Fixes

  1. Split enabledPlugins into two settings — one for "authorize --channels dispatch" and one for "auto-load on every session." This is the minimal change that would resolve the catch-22.
  2. Add a --no-plugins flag — a lighter alternative to --bare that suppresses plugin auto-loading without disabling hooks, CLAUDE.md, or auto-memory.
  3. Make the singleton guard cooperative — instead of unconditional SIGTERM, use a UNIX socket or lock file with session affinity, so concurrent sessions' plugins can coexist or gracefully defer to the existing instance.
  4. Fix the orphan watchdog for Bun on macOS — use a polling mechanism that reads /proc or calls kill(ppid, 0) instead of relying on process.ppid, which is a cached startup value in Bun.

Environment

  • Claude Code: v2.1.110
  • Plugin: telegram@claude-plugins-official v0.0.6
  • Runtime: Bun 1.3.11
  • OS: macOS (Darwin)
  • Use case: Multiple concurrent claude sessions on the same machine (legitimate workflow — e.g., multiple projects, automation, agent orchestration)

Related Issues

  • #47153 — Telegram plugin reliability
  • #37072, #37301, #37933, #36502, #36411 — Various --channels / plugin issues

extent analysis

TL;DR

Splitting enabledPlugins into two separate settings or adding a --no-plugins flag can resolve the catch-22 issue with plugin auto-loading and dispatch.

Guidance

  1. Split enabledPlugins: Introduce a new setting to control --channels dispatch authorization, separate from plugin auto-loading on every session.
  2. Add a --no-plugins flag: Implement a flag to suppress plugin auto-loading without disabling hooks, CLAUDE.md, or auto-memory, as a lighter alternative to --bare.
  3. Verify the fix: Test with multiple concurrent claude sessions to ensure that the plugin is not auto-loaded unnecessarily and that --channels dispatch works as expected.
  4. Address the orphan watchdog issue: Fix the watchdog mechanism for Bun on macOS by using a polling mechanism that reads /proc or calls kill(ppid, 0) instead of relying on process.ppid.

Example

No code snippet is provided as the issue requires a design change rather than a code fix.

Notes

The suggested fixes assume that the enabledPlugins conflation bug is the primary cause of the issue. However, the secondary bug with the orphan watchdog on macOS/Bun should also be addressed to ensure plugin reliability.

Recommendation

Apply a workaround by using the --bare flag to prevent plugin loading, although this may not be suitable for all use cases due to its limitations. A more permanent solution would involve implementing one of the suggested fixes, such as splitting enabledPlugins or adding a --no-plugins flag.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix enabledPlugins conflates dispatch gate and autoloader, causing plugin SIGTERM fratricide between concurrent sessions [1 comments, 2 participants]