A Feishu (Lark) channel plugin for Claude Code, built on Claude Code's native Channel interface. Send messages to a Feishu bot and interact with Claude — right in your chat.
Uses the MCP Channel protocol to integrate Feishu as a first-class messaging channel for Claude Code, with WebSocket persistent connection mode requiring no public HTTPS endpoint.
npx feishuchannel-for-claudecode # one-command installThe killer feature: route different Feishu groups to different Claude Code instances, each working in its own project directory. A single Feishu bot serves your entire team — each group gets its own isolated Claude with full project context.
┌─ Claude Code (project-a)
Feishu Bot ──→ Router ───────┤─ Claude Code (project-b)
(single WebSocket)└─ Claude Code (project-c)
▲ auto-connect via Unix socket
How it works:
- The router holds the single Feishu WebSocket connection and routes messages by
chat_id → workdir → worker - Each worker (server.ts) runs inside a Claude Code instance, registered by its working directory
- The first
claude-feishulaunch auto-spawns the router — no manual setup needed - When all workers disconnect, the router auto-shuts down after a grace period
Zero-config startup — just run claude-feishu in each project directory:
cd /path/to/project-a && claude-feishu # spawns router + connects as worker
cd /path/to/project-b && claude-feishu # connects to existing router
cd /path/to/project-c && claude-feishu # connects to existing routerMap Feishu groups to project directories in ~/.claude/channels/feishu/access.json:
See Multi-Group Router Setup for full configuration details.
- Multi-group routing — One Feishu bot serves multiple Claude Code instances, each in its own project
- Auto-managed router — Router spawns on first launch, shuts down when all workers disconnect
- Direct messages — Chat with Claude through Feishu DMs
- Group chats — Add the bot to group chats with @mention support
- Access control — Pairing-based onboarding, allowlists, and per-group policies
- Confirm cards — Interactive confirmation cards for risky actions
- Permission cards — Interactive approve/deny cards for tool permission requests
- Unanswered reminders — Auto-nudges Claude if a message goes unanswered for 30+ minutes (up to 3 times, escalating intervals)
- Attachments — Send and receive files and images
- Reactions — Configurable emoji reactions on message receipt
- Smart connection — Only connects when launched as a channel, skipping unnecessary connections
- Graceful shutdown — Detects parent process exit via ppid polling, preventing orphaned processes
- Bun runtime
- Claude Code CLI installed
- A Feishu (or Lark) workspace with admin access to create apps
-
Go to Feishu Open Platform (or Lark Open Platform for international)
-
Create a Custom App (enterprise internal app)
-
Note the App ID (
cli_...) and App Secret -
Under Events & Callbacks, configure two separate tabs:
Event Configuration tab:
- Switch connection method to Using persistent connection (WebSocket mode) at the top of the page
- Add event:
im.message.receive_v1
Callback Configuration tab:
- Also switch to Using persistent connection (WebSocket mode)
- Add callback:
card.action.trigger(Card interaction callback) — required for confirm card buttons to work
-
Under Permissions & Scopes, add:
Permission Purpose im:messageSend messages im:message.receive_v1Receive messages im:message.p2p_msg:readonlyRead DM messages im:message.group_at_msg:readonlyRead group @mentions im:chat:readonlyRead chat metadata im:resourceDownload attachments -
Publish the app version so permissions take effect
One command:
npx feishuchannel-for-claudecodeThis clones the repo, installs dependencies, registers the Claude Code plugin, and creates the claude-feishu shortcut — all automatically.
Manual installation
git clone https://github.com/phxwang/feishuchannel-for-claudecode.git
cd feishuchannel-for-claudecode
bun install
claude plugin marketplace add .
claude plugin install feishu@feishu-localclaude-feishuOn subsequent launches, claude-feishu automatically resumes the session named after the current directory (e.g., a session named ccmyproject is matched in the myproject/ directory). If no matching session is found, an interactive session picker opens.
Or use the full command:
claude --dangerously-load-development-channels plugin:feishu@feishu-localIn your Claude Code terminal:
/feishu:auth cli_YOUR_APP_ID YOUR_APP_SECRET
Credentials are stored in ~/.claude/channels/feishu/.env (mode 600).
-
Open Feishu and search for your bot by app name
-
Send any message to the bot
-
The bot replies with a pairing code
-
In Claude Code, run:
/feishu:access pair <code> -
The bot confirms: "Paired! Say hi to Claude."
You're ready — send messages to the bot and Claude will respond.
Add workdir to each group in ~/.claude/channels/feishu/access.json:
{
"groups": {
"oc_groupA": {
"requireMention": true,
"allowFrom": [],
"workdir": "/path/to/project-a"
},
"oc_groupB": {
"requireMention": true,
"allowFrom": [],
"workdir": "/path/to/project-b"
}
},
"defaultWorkdir": "/path/to/default-project" // DMs route here
}In separate terminals, start Claude in each project directory:
cd /path/to/project-a
claude-feishu
cd /path/to/project-b
claude-feishuThe first instance auto-spawns the router. Subsequent instances connect as workers. The router matches incoming messages by chat_id → workdir → connected worker.
Manual router start (optional): Run
bun run routerin the plugin directory before starting any Claude instances.
kill -USR1 $(pgrep -f 'bun router.ts')
cat ~/.claude/channels/feishu/router-debug.log | tail -10All access commands are run in your Claude Code terminal via /feishu:access.
/feishu:access
| Policy | Behavior |
|---|---|
pairing (default) |
Unknown users get a pairing code to approve |
allowlist |
Unknown users are silently dropped |
disabled |
All messages dropped |
/feishu:access policy allowlist
Tip: Once all your users are paired, switch to
allowlistto prevent unsolicited pairing requests.
# Approve a pairing
/feishu:access pair <code>
# Deny a pairing
/feishu:access deny <code>
# Manually allow a user by open_id
/feishu:access allow ou_xxxxxxxxxxxxxxxxxxxx
# Remove a user
/feishu:access remove ou_xxxxxxxxxxxxxxxxxxxxGroups are off by default. The bot must be added to the group by a group admin first.
# Enable a group (responds on @mention only)
/feishu:access group add oc_xxxxxxxxxxxxxxxxxxxx
# Respond to all messages (no @mention needed)
/feishu:access group add oc_xxxxxxxxxxxxxxxxxxxx --no-mention
# Restrict to specific users within the group
/feishu:access group add oc_xxxxxxxxxxxxxxxxxxxx --allow ou_id1,ou_id2
# Remove a group
/feishu:access group rm oc_xxxxxxxxxxxxxxxxxxxx# React to received messages with an emoji (default: Get)
/feishu:access set ackReaction Get
# Set max characters per message chunk
/feishu:access set textChunkLimit 4096
# Custom mention patterns for group chats
/feishu:access set mentionPatterns ["@claude","@assistant"]~/.claude/channels/feishu/
├── .env # App credentials (FEISHU_APP_ID, FEISHU_APP_SECRET)
├── access.json # Access control state (auto-managed)
├── approved/ # Pairing approval signals (transient)
├── inbox/ # Downloaded attachments
├── debug.log # Server debug log
├── router-debug.log # Router debug log (when using router)
└── router/ # Router message inboxes (when using router)
└── <chat_id>/ # Per-group message files
| Variable | Required | Description |
|---|---|---|
FEISHU_APP_ID |
Yes | Feishu app ID (cli_...) |
FEISHU_APP_SECRET |
Yes | Feishu app secret |
FEISHU_ENCRYPT_KEY |
No | Event payload encryption key |
FEISHU_ACCESS_MODE |
No | Set to static to disable pairing |
FEISHU_STATE_DIR |
No | Override state directory path |
FEISHU_CHAT_ID |
No | Set by router — puts server.ts in worker mode |
The plugin detects whether it's running under a Feishu channel Claude instance by walking up the process tree and checking for --dangerously-load-development-channels with feishu in the ancestor's command line. Non-channel Claude instances (e.g., regular claude or claude --channels plugin:discord@...) skip the Feishu WebSocket connection entirely, keeping the MCP tools available without unnecessary remote connections.
When the parent Claude process exits, the plugin detects the ppid change within 2 seconds and shuts down gracefully. This prevents orphaned bun server.ts processes from consuming 100% CPU — a workaround for Bun not reliably firing stdin end/close events on broken unix domain sockets.
bun testTests cover access control (gate logic), text chunking, mention detection, permission reply parsing, confirm code generation, chat authorization, and router workdir resolution.
- Credentials are stored with
chmod 600— only the owner can read them - Pairing codes expire after 1 hour
- After 2 unapproved messages, senders are silently dropped until the code expires
- Access mutations can only be made from the Claude Code terminal — never from channel messages (prompt injection protection)
- Group chats require explicit opt-in per group
MIT
{ "groups": { "oc_groupA": { "workdir": "/path/to/project-a", ... }, "oc_groupB": { "workdir": "/path/to/project-b", ... } }, "defaultWorkdir": "/path/to/default-project" // DMs route here }