cc-discord CLAUDE.md

cc-discord CLAUDE.md is an instructions file for Claude Code from haileyok/cc-discord. It costs 3,977 tokens per session, scanned B, original, MIT.

Repository instructions for cc-discord, a Python service that connects Claude Code sessions with Discord. They document its location, tools, architecture, deployment paths, and important setup details.

In plain words
What is it for?
Use them when developing, testing, running, deploying, or troubleshooting the claude-discord-bridge service.
Why use it?
They help coding agents work in the correct repository and avoid using tools or settings from an unrelated monorepo. They also highlight mistakes that could stop Discord messages, tests, or notification hooks from working.

Instructions file for Claude Code

Written for Claude Code: PreToolUse hook event. Also seen: reads .claude/ paths; mentions CLAUDE.md; mentions subagents.

Not installable: its command points at a path on the author’s own machine, so it runs nowhere else. The line is /home/discord/claude-discord-bridge.

Install

Getting it into your agent

There is no command for this one: it runs only inside a plugin, and the catalogue could not identify which plugin ships it. The source is linked below.

Made for: Claude Code.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for cc-discord CLAUDE.md

README.md
[![agentmods](https://agentmods.dev/badge/instructions/haileyok/cc-discord/claude-md.svg)](https://agentmods.dev/instructions/haileyok/cc-discord/claude-md)
Your own site
<a href="https://agentmods.dev/instructions/haileyok/cc-discord/claude-md"><img src="https://agentmods.dev/badge/instructions/haileyok/cc-discord/claude-md.svg" alt="Measured on agentmods" height="20"></a>
Per session 3,977 This file is loaded in full into every session.
When invoked 3,977 The same file — it is already loaded in full.
Security scan B 2 findings. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.03977 $0.03977
Opus 5 $0.01988 $0.01988
Sonnet 5 $0.00795 $0.00795
Haiku 4.5 $0.00398 $0.00398

Measured 5d ago against content hash da7ede0b643a, method: parsed. Prices are Anthropic first-party input rates as of 2026-09-06, from the pricing page.

Security

Grade B, and why

cc-discord CLAUDE.md scanned grade B with 2 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 5d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Reads agent configuration directoriesmediumAgent snooping

.claude/, .codex/, .gemini/ hold keys, settings and other credentials a mod has no legitimate need for.

- **`cli doctor` checks settings.json hook paths against `bridge.__file__`.** If you `uv tool install .` the bridge into `~/.local/bin`, `bridge.__file__` resolves into the uv tool venv, not this repo. The doctor's hook-

Runs shell commandslowCapability

Expected in a hook, worth knowing in a rule or an instructions file.

- **zellij is client-server: env on the `zellij run` subprocess is invisible to the spawned command.** The spawned process inherits the *server's* env (set when the user originally started zellij), not the bridge daemon'
CLAUDE.md · 63 lines

How it starts

The opening of the file, as written. The whole thing — 63 lines — stays where its author put it; the contents beside it link to each section on GitHub.

claude-discord-bridge

Localhost HTTP bridge between Claude Code sessions and Discord. Single-process Python daemon — aiohttp server and discord.py client share one asyncio event loop.

Freshness: 2026-05-09

Repo location and tooling

This repo lives at /home/discord/claude-discord-bridge, outside the /home/discord/discord monorepo. clyde, clint, the monorepo's pre-commit hooks, and Buildkite CI do not apply here. Don't import from or symlink into the monorepo.

Python is pinned to 3.12 via uv (.python-version). The system python3 is 3.10 — always invoke through uv:

  • Tests: uv run pytest (not pytest)
  • Run daemon in foreground: scripts/run-foreground.sh (uses uv run)

Gotchas

  • MESSAGE_CONTENT privileged intent. bot.py sets intents.message_content = True because reply routing reads message text. The bot user in the Discord Developer Portal must have this intent enabled, or on_message payloads arrive empty. The init wizard prints a reminder; agents adding new gateway features should not forget it.
  • Hooks must always exit 0. hooks/notify-stop.py and hooks/notify-notification.py wrap main() in try/except: pass; finally: sys.exit(0) on purpose — a Claude Code Stop/Notification hook that fails non-zero degrades the user's session. Preserve that contract when editing.
  • FIFO ordering for /v1/ask is enforced by AskLockMap, not Listener. Listener.register() raises RuntimeError if a thread already has a pending ask — this is an invariant guard, not the queueing mechanism. The per-thread asyncio.Lock in server.AskLockMap must be acquired before posting the question and released after unregister. Any new /v1/ask-style endpoint must follow the same lock-then-register pattern.
  • Single event loop, shared by aiohttp + discord.py. Long blocking work (sync DB calls, time.sleep, requests) inside any handler starves both the HTTP server and the Discord gateway. Use the async equivalents (aiosqlite, asyncio.sleep, aiohttp client).
  • SKILL.md in skills/ is symlinked into ~/.claude/skills/ask-discord/SKILL.md. Edit the file in this repo; the live skill follows. Don't duplicate.
  • cli doctor checks settings.json hook paths against bridge.__file__. If you uv tool install . the bridge into ~/.local/bin, bridge.__file__ resolves into the uv tool venv, not this repo. The doctor's hook-path check expects <repo>/hooks/notify-*.py paths in ~/.claude/settings.json to match wherever the package is currently importing from. Run doctor from the same install you registered hooks against.
  • Task-scoped settings via --settings flag, not env var. Discord-driven sessions (/start slash command) generate a per-task settings file at ~/.local/state/claude-discord-bridge/task-settings/<task_id>.json and pass it via claude --settings <path>. Hooks accumulate (merge), not override — the user's existing ~/.claude/settings.json hooks (e.g. notify-stop.py, notify-notification.py) still fire alongside the task-scoped hooks. The bridge's event.py hook is idempotent, so duplicate fires from both sources are harmless.
  • PreToolUse is registered ONLY for AskUserQuestion and ExitPlanMode. _on_pre_tool_use dispatches _handle_ask_user_question / _handle_exit_plan_mode directly from the hook body's structured tool_input — that's the only moment we have access to the question text and option list (Claude doesn't flush the tool_use to the JSONL until the user answers, and the Notification body just says "Claude Code needs your attention" with no question content). The hook returns no permissionDecision so the tool still proceeds to TUI block; the user's Discord reaction reply gets typed into the pane via the existing TUI handler flow. All other PreToolUse traffic is unhooked so auto-mode's classifier still drives approvals. _on_notification skips dispatch when a TUI handler is already running, so PreToolUse + the trailing Notification don't double-post.
  • Bridge zellij session is shared and configurable. Default name is meow; override with BRIDGE_ZELLIJ_SESSION. Each task is one named tab (cc-<task_id_prefix>). Don't zellij kill-session <name> while tasks are running — it kills every tab at once and the bridge will mark them all crashed on next event.
  • Self-attach panic. zellij ≥ 0.43 panics if the daemon calls zellij attach --create-background <name> for the session it's already running inside. zellij._running_inside_target_session() checks ZELLIJ_SESSION_NAME and skips the attach when colocated. New code that wants to ensure the session is alive should call ensure_session_alive(), not invoke attach directly.
  • zellij is client-server: env on the zellij run subprocess is invisible to the spawned command. The spawned process inherits the server's env (set when the user originally started zellij), not the bridge daemon's env. To inject task vars, use the env(1) prefix in the spawn argv — ZellijManager.spawn_task does this for CC_DISCORD_TASK_ID / BRIDGE_URL. Do not rely on subprocess.Popen(..., env=...) for anything that needs to reach the spawned pane.
  • load_from_db defers Discord posts to flush_startup_notices(). Reconcile-against-zellij happens before the HTTP server accepts requests, but the Discord bot logs in later — so self._bot is None during reconcile. Stage notices via _pending_startup_notices; the caller flushes them after bot.is_ready. Don't add self._bot.* calls inside the reconcile branch.
  • Assistant content streams at PostToolUse boundaries, not all at Stop. _stream_assistant_progress walks the transcript at each PostToolUse and again at Stop, posting each new assistant entry's text/thinking blocks. Per-task Task.posted_assistant_uuids dedupes on entry uuid — cleared on UserPromptSubmit and SessionStart (new turn). The set lives in memory only; if the bridge restarts mid-turn, the new daemon may re-post entries already seen. Acceptable trade-off for the simpler design.
  • Stop hook fires before Claude flushes the final assistant entry. _on_stop calls _wait_for_transcript_stable (waits up to _STOP_TRANSCRIPT_RETRY_SECS, default 10s, for the file's size to stay constant for 250ms) before the final stream pass. Tests override the retry seconds to 0.0 when verifying static-transcript branches.
  • Edit / MultiEdit / Write get a fenced diff/content block alongside the one-liner summary. tool_summary.diff_block produces the block; _post_tool_diff sends it as a separate Discord post (the aggregator coalesces summaries; diffs are individual messages). Bodies truncate at ~1920 chars to stay under Discord's 2000-char limit.
  • Subagent activity is collated into per-agent live-edited Discord embeds. Each subagent (Claude's agentId) gets one embed (title = attribution, description = last 5 actions, footer = "running|finished · N actions · Ns", color = yellow→green) edited in place. Modern CC writes subagent activity to separate <session>/subagents/agent-*.jsonl files; _refresh_subagent_blocks scans them on each PostToolUse / Stop / SubagentStop, creates a SubagentBlock per file, and Bot.edit_message(embed=…) updates the running embed. Edits are throttled to ~1.5s per block to stay under Discord's per-channel edit rate limit. PostToolUse events classified as sidechain (via _is_sidechain_tool, which also checks subagent files) are suppressed from the main aggregator. Blocks are cleared on UserPromptSubmit / SessionStart.
  • TodoWrite renders as a checklist alongside the one-liner summary. tool_summary.diff_block("TodoWrite", input) formats the todos list into ✅/▶️/⬜ marks with content text; _post_tool_diff emits it as a separate Discord message after the aggregated summary line. Subagent TodoWrite calls only surface inside the subagent block as • 📋 TodoWrite: N/M done (no full checklist) to keep blocks compact.
  • Discord attachments are saved under ~/.local/state/claude-discord-bridge/attachments/<task_id>/ and their absolute paths are appended to the relayed user message — one per line, no - bullet prefix (a leading dash is fatal to zellij action write-chars, see the zellij architecture entry). Claude reads them with the Read tool (which handles images, PDFs, JSON, plain text). Filenames are sanitized to basename and prefixed with msg_id to avoid collisions. No size cap beyond Discord's own — large files just stream through Attachment.read().
  • Audio attachments are split off and transcribed before reaching the agent. voice.transcribe() auto-selects a backend: if WISPR_FLOW_API_TOKEN is set it uses the Wispr Flow REST API (16kHz PCM WAV, base64-encoded, POST https://api.wisprflow.ai/transcribe); otherwise it shells out to a local CLI (default whisper from pip install -U openai-whisper, override binary via BRIDGE_WHISPER_BIN, model via BRIDGE_WHISPER_MODEL, default base). Successful transcriptions become [voice memo] <text> blocks in the relayed prompt — they do NOT appear in the attached-files list. On failure (no backend, ffmpeg missing, CLI missing, HTTP error, timeout) we fall back to [voice memo received — transcription unavailable; raw file: <path>] so the user knows it didn't transcribe but the file is on disk.
  • Agent → Discord file attachments use the [[attach: <path>]] marker. When a streamed assistant text block contains [[attach: /absolute/path]], _parse_attach_markers strips the marker, resolves the path (must be absolute and exist), and Bot.post_with_attachments uploads up to 10 files per Discord message alongside the cleaned text. Useful for screenshots / generated images / log dumps the agent wants to surface visually. Convention is not auto-injected into the agent — instruct claude per-conversation, or add the convention to the project's CLAUDE.md.
  • Attachment cleanup is paired with task-settings cleanup via _cleanup_task_artifacts(task_id). Every lifecycle terminal (stop, kill, crash, archive) calls it so the two on-disk artifacts can't drift apart. In addition, sweep_old_attachments() runs at daemon startup and hourly, deleting any file older than BRIDGE_ATTACHMENT_TTL_SECS (default 7 days) and removing the now-empty per-task dir.
  • /rename auto-generates names by shelling out to claude -p. TaskRegistry.generate_thread_name reads the first user prompt + first assistant response from the transcript, builds a short kebab-case naming prompt, and runs claude -p <prompt> in a subprocess (30s timeout). Uses whatever auth/model the user's claude CLI is configured with — no separate Anthropic API key needed. Empty stdout / non-zero exit → returns None and the slash command surfaces a "pass a name explicitly" error.
  • /restart uses --settings + --resume. The /restart <task-id> command spawns a new pane with both claude --settings <path> (to wire the task hooks) and claude --resume <session_id> (to pick up from the prior session). Don't manually delete ~/.claude/projects/... for a session the bridge is using.

Read the full file on GitHub · 63 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 5d ago First seen · 63 lines · 3,977 tokens per session scan B da7ede0b643a

Subscribe to this mod's changes

cc-discord CLAUDE.md is an instructions file published in the GitHub repository haileyok/cc-discord (5 stars, last pushed 13d ago), licensed MIT. It adds 3,977 tokens to every session, about $0.0199 per session on Opus 5. A static security scan graded it B with 2 findings (reads agent configuration directories, runs shell commands). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.