Help us improve
Share bugs, ideas, or general feedback.
From feishu-channel
Understand and operate Feishu channel ownership handoff between a dispatcher and teammate sessions. Use when the user wants to talk directly to a teammate through the Feishu channel, hand the channel back to the dispatcher, check who currently owns the channel, recover a dead teammate owner, or asks about "飞书 channel 交接 / channel ownership / 交还 dispatcher / 让我和 TM 直接说 / 把 channel 还给 dispatcher".
npx claudepluginhub excitedjs/claudemux --plugin feishu-channelHow this skill is triggered — by the user, by Claude, or both
Slash command
/feishu-channel:handoffThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
The Feishu channel has one global long connection, owned by the daemon. Claude
Creates p5.js generative art with seeded randomness, noise fields, and interactive parameter exploration. Use for algorithmic art, flow fields, or particle systems.
Share bugs, ideas, or general feedback.
The Feishu channel has one global long connection, owned by the daemon. Claude Code sessions do not own that Feishu WebSocket directly; they connect to the daemon through proxy MCP transports. The daemon decides which proxy receives inbound Feishu messages.
There are three separate concepts. Do not merge them:
access skill for that; it is independent of handoff.Channel delivery ownership is explicit daemon state:
A proxy identifies itself as either:
dispatcher — the coordinator session that can grant, assign, and reclaim
ownership.session — an ordinary teammate session.The daemon trusts the proxy role sent at registration. If status shows no dispatcher, handoff cannot safely work: relaunch the Dispatcher with the channel plugin environment that marks it as dispatcher.
Use the MCP tool:
feishu_channel_status({})
Read these fields:
owner_session_id — the session that owns channel delivery.
dispatcher_session_id — the current Dispatcher proxy session.
granted_session_id — a teammate session allowed to acquire next, if any.
effective_target_session_id — the live session that will receive messages
right now; null means inbound rows stay pending.
lease_epoch — increments on real ownership transitions. Use it to confirm
a handoff or return actually happened.
sessions — live proxy sessions. Each carries sessionId, pid,
proxyVersion, role, and a metadata bag the proxy self-reported at
registration. The channel treats metadata as opaque string pairs; an
orchestrator fills well-known keys. A claudemux teammate reports
metadata.teammate_name (its tm name) and metadata.cwd, so you locate a
teammate by name without reverse-engineering it from pid:
feishu_channel_status().sessions.find(s => s.metadata?.teammate_name === "<name>")
The dispatcher's own session has no teammate_name; a non-claudemux session
may carry only cwd or an empty bag.
If owner_session_id names an offline teammate and
effective_target_session_id is null, messages are not lost. They stay in
the daemon queue until the owner returns or the Dispatcher reclaims ownership.
Preferred direct transfer — target the teammate by name:
feishu_channel_acquire({ "match": { "teammate_name": "<name>" } })
The Dispatcher may also assign an explicit session_id from
feishu_channel_status().sessions. Pass session_id or match, not both;
match selects the live session whose metadata equals every given key, and
errors clearly when nothing matches or when more than one does (the message
lists the candidate session ids — pass session_id to disambiguate). After the
call, run status again and check:
owner_session_id is the teammate session id.effective_target_session_id is the same teammate session id.lease_epoch increased.Now Feishu inbound messages go to that teammate's Claude context.
Use this when the product flow is: "Dispatcher tells the TM to take the channel", and the TM should perform the final acquire through its own MCP connection.
Dispatcher (by name, or by an explicit session_id):
feishu_channel_grant({ "match": { "teammate_name": "<name>" } })
Then instruct that teammate to call:
feishu_channel_acquire({})
The grant is single-target. A different teammate cannot use it, and an ungranted teammate cannot acquire the channel just by calling acquire.
The current teammate owner calls:
feishu_channel_return_to_dispatcher({})
Then check status:
owner_session_id equals dispatcher_session_id.effective_target_session_id equals dispatcher_session_id, if the
Dispatcher proxy is live.lease_epoch increased.This is the normal "let me talk to Dispatcher again" path.
If the current owner is a teammate that crashed or will not come back, status
will show owner_session_id as that teammate and effective_target_session_id
as null. The Dispatcher can force recovery:
feishu_channel_reclaim({})
Use reclaim only as recovery. For normal cooperative handoff, prefer
feishu_channel_return_to_dispatcher from the teammate owner.
no live channel proxy session — the target session id is not connected.
Recheck status and use a live id.no live channel proxy matching — no live session's metadata matches the
match selector. Recheck status; the teammate may not be connected, or may be
an older proxy that reports no teammate_name.ambiguous match — more than one live session matched. The message lists the
candidate session ids; pass one as session_id to disambiguate.pass only one of session_id / match — supply a single selector, not both.channel ownership was not granted by the dispatcher — an ordinary teammate
tried to acquire without a Dispatcher grant. Ask the Dispatcher to grant or
directly assign.only the dispatcher may ... — the tool must be run from the Dispatcher
proxy, not an ordinary teammate.no live dispatcher channel proxy is registered — return/reclaim cannot
complete because the daemon does not see a Dispatcher. Relaunch or reconnect
the Dispatcher first.