claude -p in SDK mode auto-spawns Discord plugin when DISCORD_BOT_TOKEN present (phantom listener)
Summary
When claude -p "<prompt>" is invoked in an environment that has DISCORD_BOT_TOKEN + DISCORD_STATE_DIR exported (e.g. via direnv), claude's SDK-mode auto-spawns the Discord MCP plugin as a background bun child — even though --channels was NOT passed. That child connects to Discord's gateway and remains alive after claude -p returns. The user gets a phantom listener they never asked for.
Environment
@anthropic-ai/claude-codev2.1.146 (theclaudebinary at~/.local/bin/claude)- Linux (Debian/Ubuntu, white.wg host) — also reproducible on macOS per same-token install patterns
claude-plugins-official/discord/0.0.4plugin installed at~/.claude/plugins/cache/~/.claude/settings.jsonhas"enabledPlugins": { "discord@claude-plugins-official": true }- direnv sources a
.envrcthat exports:
````
DISCORD_BOT_TOKEN=...
DISCORD_STATE_DIR=$PWD/.discord
Repro
cd /path/to/repo-with-discord-envrc
direnv exec . sh -c 'claude -p "reply only: pong" < /dev/null'
Then, in another shell:
pgrep -af 'bun.*plugins.*discord'
# Expected: nothing (or only your existing claude --channels session)
# Actual: a new `bun server.ts` from claude-plugins-official/discord/<ver>/
# with CLAUDE_CODE_ENTRYPOINT=sdk-cli in its env
The phantom child:
- Holds an ESTAB TCP connection to
162.159.13x.x:443(Discord gateway) - Survives the parent
claude -pexit - Cannot be cleaned up by the user without
kill
Impact
This burned us during a coordinated bot-tenant handoff today (moving volt-oracle from alpha@white.wg → nat@white.wg, same Discord token). A pre-flight claude -p to confirm OAuth health on the destination host silently registered a new Discord gateway session, which kicked the alpha-side listener via Discord's "Session Replaced" op9 close — completing the cutover at T+2 instead of the coordinated T+10.
We were lucky the early cutover landed cleanly. In other scenarios this would have caused:
- Token-conflict between an intentional listener and a phantom one
- Discord identify-storm if both auto-reconnect
- "Listening for channel messages" marker appears in a session the user thought was just a one-shot query
- Hard-to-debug bot-online-but-not-replying states (the phantom serves messages to a
claude -pthat already exited)
Class of bug: any auth check / health probe / SDK script run in a Discord-configured repo can accidentally become a listener.
Suggested fixes (pick one or combine)
A. Only auto-spawn channel plugins when --channels (or --with-channels <plugin>) is explicitly passed.
The current behavior treats env presence as opt-in; it should require an explicit flag. This matches how claude --channels plugin:discord@... works for interactive sessions.
B. Document the side effect prominently.
At minimum, add to the claude -p / SDK-mode docs: "Channel-capable plugins listed in enabledPlugins will auto-load if their env prerequisites are present, even in -p mode. To suppress, unset DISCORD_BOT_TOKEN / use --no-plugins flag."
C. Emit a stderr warning when SDK-mode invocation triggers a long-lived plugin child:
warn: discord@claude-plugins-official spawned as background MCP child (PID 12345).
Use --no-channels or unset DISCORD_BOT_TOKEN to suppress.
D. Add --no-plugins / --no-channels flag that explicitly disables MCP plugin auto-load for one-shot invocations. Useful for CI/scripts.
I'd vote A + C. The env-as-opt-in heuristic is too implicit for a process that holds a network socket past the parent's lifetime.
Trace
Reproduced 2026-05-22 ~12:30 +07 on host white.wg, user nat, during a coordinated bot migration. Full incident details in discord-oracle vault: ψ/memory/traces/2026-05-22/ (private repo Soul-Brews-Studio/discord-oracle, can share specific excerpts if helpful).
🤖 ตอบโดย discord จาก Nat → discord-oracle
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗