繁體中文 | English
Claude Code's channel system lets messaging platforms connect directly to a Claude Code session. This plugin adds LINE support — the only third-party LINE channel plugin available. Official support currently covers only Discord and Telegram.
Connect a LINE bot to your Claude Code with an MCP server. When someone messages the bot, the server forwards the message to Claude and provides tools to reply. Claude can respond to DMs and group chats, download media, and send images — all from within a Claude Code session.
- Bun — the MCP server runs on Bun. Install with
curl -fsSL https://bun.sh/install | bash. - A publicly accessible HTTPS endpoint for the webhook (e.g. behind nginx or Caddy).
Single-user DM setup. See ACCESS.md for groups and multi-user setups.
1. Create a LINE Official Account and enable Messaging API.
As of September 2024, Messaging API channels can no longer be created directly from the LINE Developers Console. The new flow:
- Sign in to LINE Official Account Manager and create a LINE Official Account.
- In the account settings, find Messaging API and click Enable. Select or create a Provider when prompted.
- Open LINE Developers Console and navigate to the channel that was automatically created under your Provider.
On the channel page in LINE Developers Console:
- Basic settings tab → copy the Channel secret
- Messaging API tab → click Issue to generate a Channel access token (long-lived) and copy it
Back in LINE Official Account Manager, configure three pages under Settings:
Settings → Messaging API (/setting/messaging-api)
- Set the Webhook URL to your public HTTPS endpoint (e.g.
https://line-webhook.example.com/webhook) - Click Save
Settings → Account settings (/setting) — only needed for group support
- Under Feature toggles, set Join groups and multi-person chats to Accept invitations
Settings → Response settings (/setting/response)
- Webhook: ON (enables LINE to deliver events to your webhook URL)
- Chat response method: set to Manual chat (not "Manual chat + Auto-response")
- Greeting message: turn OFF — Claude handles the follow event via webhook instead
2. Install the plugin.
These are Claude Code commands — run claude to start a session first.
Add the plugin marketplace:
claude plugin marketplace add NYCU-Chung/claude-line-channel
Install the plugin:
claude plugin install line@claude-line-channel
3. Configure credentials.
mkdir -p ~/.claude/channels/line
cat > ~/.claude/channels/line/.env << 'EOF'
LINE_CHANNEL_ACCESS_TOKEN=<your long-lived access token>
LINE_CHANNEL_SECRET=<your channel secret>
LINE_WEBHOOK_PORT=3456
EOF
chmod 600 ~/.claude/channels/line/.envTo run multiple bots on one machine (different tokens, separate allowlists), point
LINE_STATE_DIRat a different directory per instance.
4. Expose the webhook.
Use nginx, Caddy, or any reverse proxy to forward HTTPS to http://localhost:<LINE_WEBHOOK_PORT>. Example nginx config:
server {
listen 443 ssl;
server_name line-webhook.example.com;
# ... SSL certificate config ...
location /webhook {
proxy_pass http://localhost:3456/webhook;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Line-Signature $http_x_line_signature;
}
}After deploying, go back to Settings → Messaging API in LINE Official Account Manager, paste the URL, and click Save. Then use the Verify button in LINE Developers Console to confirm it returns HTTP 200.
5. Set up a session CLAUDE.md (recommended).
Copy the included template to your working directory. Claude will read it on startup and know how to behave as a LINE bot — including reading history.log for context after restarts.
cp ~/.claude/plugins/cache/claude-line-channel/line/*/examples/CLAUDE.md ~/my-line-bot/CLAUDE.mdCustomize it to fit your use case (persona, language, rules, etc.).
6. Relaunch with the channel flag.
The server won't connect without this — exit your session and start a new one:
cd ~/my-line-bot
claude --dangerously-load-development-channels server:line7. Allow your LINE user ID.
Create ~/.claude/channels/line/access.json:
{
"dmPolicy": "allowlist",
"allowFrom": ["Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],
"groups": {}
}To find your LINE user ID: add the bot as a friend and send it any message. Check ~/.claude/channels/line/unknown-dms.log — your user ID appears there on first contact. Add it to allowFrom and message again. (Unknown group IDs go to unknown-groups.log instead.)
Steps 5–7 assume you run Claude from a dedicated directory (
~/my-line-bot/). TheCLAUDE.mdin that directory is loaded automatically on session start.
Once setup is complete, Claude Code runs as a persistent session that listens for LINE messages.
DMs
Add the bot as a friend on LINE and send it a message. The plugin forwards the message to Claude, which responds directly in the same conversation. No additional configuration is required beyond the allowlist entry in access.json.
Groups
- Add the bot to a LINE group.
- The bot's group ID will appear in
~/.claude/channels/line/unknown-groups.logon first message. - Add it to
access.jsonundergroups:
{
"dmPolicy": "allowlist",
"allowFrom": ["Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"],
"groups": {
"Cxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": {
"requireMention": true,
"allowFrom": []
}
}
}With requireMention: true, Claude only responds when @mentioned in the group. If @mention isn't available (e.g. older LINE clients), you can also trigger it with a keyword by adding mentionPatterns:
{
"groups": {
"Cxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": {
"requireMention": true,
"allowFrom": []
}
},
"mentionPatterns": ["^Claude", "\\bclaudebot\\b"]
}Any message matching one of the regex patterns counts as a mention. Set requireMention: false to respond to every message in the group.
Customizing Claude's behavior
Copy examples/CLAUDE.md to your working directory and edit it. This file is loaded automatically when Claude Code starts — use it to set a persona, language, response style, or any rules specific to your use case.
cp examples/CLAUDE.md ~/my-line-bot/CLAUDE.md
# then edit ~/my-line-bot/CLAUDE.mdKeeping context across restarts
Claude Code maintains a rolling history.log at ~/.claude/channels/line/history.log. The included CLAUDE.md template instructs Claude to read it on startup, so conversation context is preserved even after the session restarts.
See ACCESS.md for DM policies, group configuration, mention detection, and the full access.json schema.
Quick reference: LINE user IDs start with U, group IDs with C, room IDs with R. Default policy is allowlist — messages from unknown users are dropped silently.
⚠️ Security trap:allowFrom: [](empty array) does not mean "block everyone" — it means allow everyone. The check is skipped when the list is empty. Always put at least one user ID inallowFrombefore exposing the webhook publicly, or setdmPolicy: "disabled"to block all DMs until you're ready.
⚠️ Machine access: Claude Code has full access to your machine. Treat the LINE bot'sallowFromlist the same way you'd treat SSH authorized keys — only add LINE user IDs you trust completely.
| Tool | Purpose |
|---|---|
reply |
Send a text message to a DM or group chat. Takes chat_id + text. Auto-chunks long messages using the free Reply API (within 25 s of the inbound message), falls back to Push API. |
get_content |
Download a media message (image/video/audio/file) sent by a LINE user to the inbox directory. Returns the file path; images also return an inline preview. |
send_image |
Send an image to a LINE chat via a publicly accessible HTTPS URL. |
upload_file |
Upload a file from the inbox directory only to gofile.io with a password and expiry. Returns the download link and password. |
fetch_messages |
LINE does not expose a message history API for bots — this tool returns a note about that limitation. |
To run multiple Claude Code sessions sharing one LINE channel, use examples/line-router.ts. It verifies the HMAC signature once and fans the webhook out to each session's port.
# Session 1: LINE_WEBHOOK_PORT=3461
# Session 2: LINE_WEBHOOK_PORT=3462
# Router listens on 3456 and forwards to both
LINE_CHANNEL_SECRET=<secret> bun examples/line-router.tsFor long-running deployments, use tmux to keep sessions alive across SSH disconnects. A watchdog script handles automatic restarts when the MCP server dies.
~/line-dm/
├── launch.sh # tmux entry point — restart loop + rolling context pruning
├── start.sh # MCP server startup, referenced by mcpServers in .claude.json
└── CLAUDE.md # persona and instructions for Claude
#!/bin/bash
PROJ=~/.claude/projects/$(realpath ~/line-dm | sed 's|/|-|g' | sed 's|^-||')
MAX_SIZE=3000000
JSONL=$(ls "$PROJ"/*.jsonl 2>/dev/null | head -1)
if [ -n "$JSONL" ] && [ "$(wc -c < "$JSONL")" -gt "$MAX_SIZE" ]; then
LINES=$(wc -l < "$JSONL")
KEEP=$(( LINES * 2000000 / $(wc -c < "$JSONL") ))
tail -n "$KEEP" "$JSONL" > "$JSONL.tmp" && mv "$JSONL.tmp" "$JSONL"
fi
CONTINUE=""
ls "$PROJ"/*.jsonl 2>/dev/null | grep -q . && CONTINUE="--continue"
while true; do
claude --dangerously-skip-permissions $CONTINUE \
--dangerously-load-development-channels server:line
CONTINUE="--continue"
echo "Claude exited, restarting in 5 seconds..."
sleep 5
doneReferenced by mcpServers.line in your project entry inside ~/.claude.json. Kills any orphaned bun process holding the port before starting:
#!/bin/bash
fuser -k 3461/tcp 2>/dev/null || true
LINE_STATE_DIR=~/.claude/channels/line-dm \
LINE_WEBHOOK_PORT=3461 \
exec bun run --cwd ~/.claude/plugins/cache/claude-line-channel/line/0.1.0 startAdd a project entry to ~/.claude.json:
{
"projects": {
"/home/user/line-dm": {
"mcpServers": {
"line": {
"command": "bash",
"args": ["/home/user/line-dm/start.sh"]
}
}
}
}
}Why
mcpServersis required: the plugin system registers the server asplugin:line:line, which the channel system cannot match. AmcpServersentry ensures a server named exactlylineis available.
tmux new-session -d -s line-dm "cd ~/line-dm && bash launch.sh"The --dangerously-load-development-channels flag shows a one-time confirmation dialog on startup. A watchdog handles this and restarts Claude when bun crashes:
#!/bin/bash
# Run in a separate tmux session: tmux new-session -d -s watchdog "bash watchdog.sh"
GRACE=0
while true; do
PANE_PID=$(tmux list-panes -t line-dm -F '#{pane_pid}' 2>/dev/null | head -1)
# Auto-confirm the development channels dialog
tmux send-keys -t line-dm "" Enter 2>/dev/null
if [ "$GRACE" -le 0 ] && ! ss -tlnp | grep -q ':3461 '; then
CLAUDE_PID=$(pstree -p "$PANE_PID" 2>/dev/null | grep -o 'claude([0-9]*)' | head -1 | grep -o '[0-9]*')
if [ -n "$CLAUDE_PID" ]; then
echo "$(date): bun MCP server down, restarting Claude ($CLAUDE_PID)"
kill "$CLAUDE_PID"
GRACE=6 # 60-second grace period before next health check
fi
fi
[ "$GRACE" -gt 0 ] && GRACE=$((GRACE - 1))
sleep 10
doneThings we discovered running this in production:
-
claude plugin installuses SSH to clone from GitHub — set up HTTPS if you don't have SSH keys. This affects everyone without a GitHub SSH key configured (including fresh VPS setups). You'll seePermission denied (publickey)orHost key verification failed. Fix it before runningplugin install:git config --global url."https://github.com/".insteadOf "git@github.com:"
-
LINE only allows one webhook URL per channel. If you want multiple Claude Code sessions to share one LINE channel (e.g. one session per group), use
examples/line-router.tsto fan out the webhook to each session's port. Without it, only one session receives messages. -
Reply tokens expire in 30 seconds. The plugin uses the free Reply API for the first response after each inbound message, then falls back to the paid Push API. If Claude takes more than 30 s to respond, the reply costs Push API quota.
-
LINE has no message history API. The bot only sees messages that arrive while it is running. Claude Code automatically maintains a rolling
history.login the state directory (~/.claude/channels/line/history.log) — instruct Claude to read it on startup to restore context after a restart. -
Bot must be a friend before users can DM it. LINE does not allow DMs to bots unless the user has added the bot as a friend first.
-
Mention detection requires the bot's user ID, which is fetched asynchronously on startup. The webhook returns HTTP 503 for a few seconds during this window — LINE will retry automatically.
-
Group IDs vs room IDs: Multi-person chats started from a group have IDs starting with
C; chats started from a direct invitation (rooms) start withR. They are different and must be configured separately inaccess.json.
- Webhook signature verified with HMAC-SHA256 using constant-time comparison (no timing side-channel)
upload_fileis restricted to the inbox directory — prompt injection via LINE messages cannot cause arbitrary file exfiltration- File upload passwords generated with
crypto.randomBytes(96-bit entropy) and sent in-band to Claude; they are not persisted by the channel .envfile chmod'd to0600on startup; state directory chmod'd to0700- Unknown group IDs sanitized before logging
- HTTP server binds to
127.0.0.1by default — put an HTTPS reverse proxy (nginx/caddy) in front. Override withLINE_BIND_HOST=0.0.0.0only if you know what you're doing. access.jsonis fail-closed: if the file is malformed, the server refuses to start; if it becomes unreadable at runtime, all messages are dropped (dmPolicy=disabled) until it is fixed.- All outbound HTTP calls (LINE API, gofile) have a 30 s timeout; GitHub version check uses a 5 s timeout and never blocks startup.
replyandsend_imageonly acceptchat_ids that have actually sent a message to the bot (restored fromhistory.logon restart, bounded to 1000 recent chat_ids) — prevents prompt injection from directing Claude to message arbitrary LINE users.get_contentvalidates thatmessage_idis numeric before interpolating into the LINE URL, caps downloads at 100 MB, and streams to disk (never holds the whole payload in memory).- Sender-controlled content written to
history.loghas newlines escaped so messages cannot forge log entries or inject prompts into a restarting Claude.
Claude found a development channel (server:line) but has no MCP server with that name. This happens because the plugin system registers the server as plugin:line:line, not line.
Fix: add a mcpServers.line entry to your project in ~/.claude.json as shown in the start.sh section above.
If the plugin cache versions are all marked orphaned, Claude will refuse to run them. Remove the markers:
rm -f ~/.claude/plugins/cache/claude-line-channel/line/*/.orphaned_atThe MCP server crashed during startup. Most common cause: an orphaned bun process is holding the webhook port. Add fuser -k <port>/tcp at the top of start.sh to clear it on each restart.
To debug manually:
LINE_STATE_DIR=~/.claude/channels/line-dm LINE_WEBHOOK_PORT=3461 bash ~/line-dm/start.sh- Confirm bun is running:
ss -tlnp | grep 3461 - Check for orphaned bun processes:
ps -u $(whoami) -o pid,ppid,cmd | grep bun - Test the webhook path directly (bypassing line-router):
Expected:
SECRET=$(grep LINE_CHANNEL_SECRET ~/.claude/channels/line-dm/.env | cut -d= -f2) PAYLOAD='{"destination":"U0","events":[{"type":"message","mode":"active","timestamp":1000000000000,"source":{"type":"user","userId":"Utest"},"webhookEventId":"ev1","deliveryContext":{"isRedelivery":false},"message":{"id":"m1","type":"text","quoteToken":"q","text":"ping"}}]}' SIG=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64) curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:3461/webhook \ -H "Content-Type: application/json" -H "x-line-signature: $SIG" -d "$PAYLOAD"
200. A403means the HMAC secret is wrong. - Watch the Claude session for the
← line ·notification:tmux capture-pane -t line-dm -p | tail -20
When running multiple sessions via line-router, check whether the router is forwarding correctly:
tmux capture-pane -t line-router -p | grep error | tail -10Unable to connect errors for unused ports are harmless — only the port matching the active session matters.
Apache-2.0