Entrypoint
entrypoint.sh runs with set -euo pipefail, so any failing step stops the container with an error. It runs on every start, and each step is safe to repeat.
1. Check required variables
Section titled “1. Check required variables”DISCORD_BOT_TOKENmust be set, otherwise the script exits.- One of
CLAUDE_CODE_OAUTH_TOKENorANTHROPIC_API_KEYmust be set, otherwise it printsSet CLAUDE_CODE_OAUTH_TOKEN (recommended) or ANTHROPIC_API_KEYand exits with code 1.
2. Create the Discord state directory
Section titled “2. Create the Discord state directory”DISCORD_STATE_DIR (default ~/.claude/channels/discord) is created with mode 700. The plugin keeps its state, including access.json, there.
3. Skip first-run prompts
Section titled “3. Skip first-run prompts”In $CLAUDE_CONFIG_DIR/.claude.json (created as {} if missing) it sets, only if not already set:
hasCompletedOnboarding = trueprojects["/workspace"].hasTrustDialogAccepted = true
This lets Claude start without the onboarding screens and trust /workspace without asking.
4. Set up git and GitHub
Section titled “4. Set up git and GitHub”Only when GH_TOKEN is set:
gh auth setup-gitmakesgit push/pullover HTTPS use the token.- If
GIT_AUTHOR_NAME/GIT_AUTHOR_EMAILare set, they are written to the global gituser.name/user.email.
5. Bridge mode: start the bridge
Section titled “5. Bridge mode: start the bridge”If BOT_MODE is bridge (the default), the script execs node /opt/bridge/dist/index.js here and the remaining steps are skipped. See Thread sessions. Any value other than bridge or plugin stops the container with an error.
The steps below run only with BOT_MODE=plugin.
6. Install the Discord plugin (first run only)
Section titled “6. Install the Discord plugin (first run only)”If claude plugin list does not show discord@claude-plugins-official, the script adds the anthropics/claude-plugins-official marketplace and installs the plugin. It is stored in the claude-home volume, so later starts skip this.
7. Write the access list from env
Section titled “7. Write the access list from env”Only when DISCORD_ALLOWED_USERS is non-empty. The environment is the source of truth and access.json is rewritten on every start:
access.json field |
Source |
|---|---|
dmPolicy |
DISCORD_DM_POLICY (default allowlist) |
allowFrom |
DISCORD_ALLOWED_USERS |
groups.<channel id>.allowFrom |
DISCORD_CHANNEL_USERS, or DISCORD_ALLOWED_USERS if empty |
groups.<channel id>.requireMention |
false only if DISCORD_REQUIRE_MENTION=false, otherwise true |
pending |
kept as is |
Comma-separated lists have whitespace removed and empty items dropped. The file is written with mode 600.
If DISCORD_ALLOWED_USERS is empty the file is left alone and access is managed by hand with the /discord:access command in the Claude terminal.
8. Start Claude Code
Section titled “8. Start Claude Code”The script execs claude (so it replaces the shell and receives signals from tini) with:
| Flag | When |
|---|---|
--channels plugin:discord@claude-plugins-official |
always |
--permission-mode $CLAUDE_PERMISSION_MODE |
always, default auto |
--model $CLAUDE_MODEL |
if set |
--effort $CLAUDE_EFFORT |
if set |
--continue |
if CLAUDE_CONTINUE is true (default) and a previous session exists in projects/-workspace/ |
--continue means a restarted container picks up the previous conversation instead of starting fresh.