Skip to content

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.

  • DISCORD_BOT_TOKEN must be set, otherwise the script exits.
  • One of CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY must be set, otherwise it prints Set CLAUDE_CODE_OAUTH_TOKEN (recommended) or ANTHROPIC_API_KEY and exits with code 1.

DISCORD_STATE_DIR (default ~/.claude/channels/discord) is created with mode 700. The plugin keeps its state, including access.json, there.

In $CLAUDE_CONFIG_DIR/.claude.json (created as {} if missing) it sets, only if not already set:

  • hasCompletedOnboarding = true
  • projects["/workspace"].hasTrustDialogAccepted = true

This lets Claude start without the onboarding screens and trust /workspace without asking.

Only when GH_TOKEN is set:

  • gh auth setup-git makes git push/pull over HTTPS use the token.
  • If GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL are set, they are written to the global git user.name / user.email.

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.

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.

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.