feishu-cli-event

Original🇨🇳 Chinese
Translated

Feishu Real-time Event Subscription (WebSocket). Use `event list` to view supported EventKeys; `event schema` to check event payload/scope; `event consume` to start long-connection subscription, where event streams are written to stdout in NDJSON format (blocking, one process subscribes to one EventKey); `event status` to view active local consume processes; `event stop` to terminate consume processes by PID / EventKey / --all. Supports over 22 EventKeys (including IM message receive/read/recall/reaction, group member changes, contact employee changes, calendar changes, cloud disk title/collaborator changes, approval instances and tasks, VC meeting start/end). Status files: ~/.feishu-cli/events/<app_id>/bus.json + flock file lock + WebSocket auto-reconnect. This skill is applicable when users request "listen to Feishu events", "real-time message event reception", "approval callback subscription", "event stream", "WebSocket long-connection listening", "event consume", "event list / schema / status / stop", "AI Agent bot real-time response". Note: This skill only handles subscription; for event webhook business logic (pushing to Feishu messages/writing to multidimensional tables), please use with feishu-cli-msg / feishu-cli-bitable.

8installs
Added on

NPX Install

npx skill4agent add riba2534/feishu-cli feishu-cli-event

SKILL.md Content (Chinese)

View Translation Comparison →

Feishu Real-time Event Subscription Skill (WebSocket)

Subscribe to Feishu Open Platform events via the
feishu-cli event
subcommand family. Use WebSocket long connections to receive events and output them to stdout in NDJSON format, suitable for scenarios like AI Agent bot real-time response, group message monitoring, approval callback consumption, etc.
feishu-cli: If not installed yet, please visit riba2534/feishu-cli for installation instructions.
Need to send messages? Please use the feishu-cli-msg skill. This skill focuses on event subscription (receiving app events) and does not handle sending.

Core Concepts

Process Model = One consume process per EventKey

event consume <EventKey>
   ├─ Starts WebSocket long connection (Feishu SDK ws.Client + AutoReconnect)
   ├─ Registers to bus.json (PID / EventKey / start time / max-events / timeout)
   ├─ Outputs [event] ready event_key=<key> to stderr
   ├─ Receives events → writes to stdout (NDJSON, one JSON per line)
   ├─ Optional: dumps each event as <event_id>.json file
   ├─ Exit conditions: --max-events / --timeout / SIGTERM / Ctrl-C / stdin EOF / pipe broken
   └─ Automatically unregisters from bus.json on exit
Differences from lark-cli event: lark-cli runs an independent bus daemon via Unix domain socket for event fan-out; feishu-cli simplifies it to "each consume connects directly to a WebSocket" without event distribution—this sufficiently covers the main scenario of AI Agent single EventKey subscription.

Status Files and Cross-Process Mutual Exclusion

PathPurpose
~/.feishu-cli/events/<app_id>/bus.json
List of active consumers (PID/EventKey/start time/parameters)
~/.feishu-cli/events/<app_id>/bus.lock
flock file lock; serializes bus.json read/write, automatically released when fd is closed
One subdirectory per AppID, different applications do not interfere with each other.
event status
will actively remove entries of non-living PIDs (residues from kill -9 / crashes). bus.json uses tmp + rename for atomic writing to prevent partial writes.

Output Protocol (NDJSON + ready marker)

StreamContent
stdoutOne JSON per event line (NDJSON), suitable for jq / script pipelines
stderrDiagnostic logs; outputs
[event] ready event_key=<key> (init complete; WS handshake in progress)
on startup
AI Agent Recommendation: Run consume in the background with the parent process (
run_in_background=true
), block on stderr until the
[event] ready
line appears before starting to read stdout. Note: The ready marker only indicates process initialization is complete, the WS handshake is executed asynchronously in the background; after seeing the marker, the parent process needs an additional 1-3s wait for the WS handshake to truly complete. In production environments, it is recommended that the parent process sends a self-check event + waits for echo loop to confirm the link is connected.

Exit Codes and Exit Reasons

Exit CodeMeaning
0Normal exit (reached
--max-events
/
--timeout
/ SIGTERM / Ctrl-C / stdin EOF)
Non-0Startup failure / unrecoverable WebSocket error / parameter error
The end of stderr will output
[event] exited — elapsed=<d> reason=<r>
, where there are 4 reasons:
  • limit
    — reached
    --max-events
  • timeout
    — reached
    --timeout
  • signal
    — context canceled (Ctrl-C / SIGTERM / stdin EOF / downstream pipe broken)
  • error
    — continuous WebSocket connection failure

Quick Command Reference

bash
feishu-cli event list [--json]                       # 1. List all supported EventKeys
feishu-cli event schema <event_key> [--json]         # 2. View EventType / scope / payload schema of a key
feishu-cli event consume <event_key> [flags]         # 3. Start subscription (blocking)
feishu-cli event status [--json]                     # 4. View active local consume processes
feishu-cli event stop {--pid N | --event-key K | --all} [--force] [--json]  # 5. Stop consume

1.
event list
: List supported EventKeys

Displays over 22 currently supported EventKeys grouped by domain (im / contact / calendar / drive / approval / vc).
bash
# Table view (default)
feishu-cli event list

# JSON output, extract all IM domain EventKeys with jq
feishu-cli event list --json | jq -r '.[] | select(.domain=="im") | .key'
Output fields (JSON mode):
key
/
event_type
/
description
/
domain
/
scopes[]
/
payload_schema
.

2.
event schema
: View payload schema and scope

bash
feishu-cli event schema im.message.receive_v1
feishu-cli event schema im.message.receive_v1 --json
Outputs 4 parts:
Key
/
Event Type
/
Domain
/
Description
/
Scopes
+ optional
Payload Schema (Example)
. The payload schema is manually curated; the actual payload after subscription is subject to Feishu Open Platform documentation.

3.
event consume
: Start WebSocket subscription (blocking)

bash
# Basic subscription, exit with Ctrl-C
feishu-cli event consume im.message.receive_v1

# Debug: capture 5 messages, run for maximum 60s
feishu-cli event consume im.message.receive_v1 --max-events 5 --timeout 60s

# Persist to disk + quiet mode
feishu-cli event consume im.message.receive_v1 --output-dir ./events --quiet

# Real-time filter group messages with jq
feishu-cli event consume im.message.receive_v1 | jq 'select(.event.message.chat_type=="group")'

# Background concurrent subscription to multiple EventKeys (one process per EventKey)
feishu-cli event consume im.message.receive_v1     > receive.ndjson  2> receive.log  &
feishu-cli event consume im.message.reaction.created_v1 > reaction.ndjson 2> reaction.log &
feishu-cli event status
Key flags:
FlagDefaultDescription
--max-events N
0 (unlimited)Exit after receiving N events, reason=
limit
--timeout 30s
0 (unlimited)Exit after running for D duration, reason=
timeout
--jq .event.xxx
""Minimal dot-path filtering, does not support full jq syntax (use pipe with external jq)
--output-dir ./events
""Additionally dump each event as
<event_id>.json
to disk (does not affect stdout)
--quiet
falseSuppress stderr diagnostics; use with caution for AI Agent—most stderr will be suppressed, but the ready marker still uses real os.Stderr and is not affected
--jq
Limitations
: Only recognizes map values in the form of
.a.b.c
(e.g.,
.event.message
), does not support
select
/ array subscripts / pipes. For complex filtering, use
feishu-cli event consume ... | jq '<expr>'
.
--output-dir
Limitations
: Must be a safe relative path; does not expand
~
, does not accept absolute paths or
..
path segments.

4.
event status
: View active local consume processes

bash
feishu-cli event status
feishu-cli event status --json | jq '.consumers[] | .pid'
Outputs:
App ID
/
State file
path /
PID
/
EVENT_KEY
/
UPTIME
/
EXTRA
(max-events / timeout / output-dir / jq).
During query, entries of non-living PIDs will be actively removed (cleaning up zombie records from kill -9 / crashes).

5.
event stop
: Terminate consume processes

bash
feishu-cli event stop --pid 12345                          # By PID
feishu-cli event stop --event-key im.message.receive_v1    # By EventKey (all processes subscribing to this key)
feishu-cli event stop --all                                # All consume processes under current AppID
feishu-cli event stop --all --force                        # SIGKILL (emergency situation)
Default uses SIGTERM for graceful exit (consume process will automatically unregister from bus.json), waits up to 3s to verify the process has exited;
--force
upgrades to SIGKILL, which will leave zombie entries in bus.json, and the next
event status
will automatically clean them up.

EventKey Quick Reference (Grouped by Domain)

Use
feishu-cli event list
for the complete list. Common ones:
DomainEventKeyDescription
im
im.message.receive_v1
Receive messages (sent to Bot by user/group chat)
im
im.message.message_read_v1
Message read receipt
im
im.message.recalled_v1
Message recalled
im
im.message.reaction.created_v1
/
deleted_v1
Message reaction added/deleted
im
im.chat.updated_v1
Group chat information updated
im
im.chat.member.user.added_v1
/
deleted_v1
User joined/left group
im
im.chat.member.bot.added_v1
/
deleted_v1
Bot added/removed from group
im
im.chat.disbanded_v1
Group chat disbanded
contact
contact.user.created_v3
/
updated_v3
/
deleted_v3
Employee hired/changed/left
calendar
calendar.calendar.event.changed_v4
Calendar event changed (created/updated/deleted)
calendar
calendar.calendar.acl.created_v4
Calendar permission changed
drive
drive.file.title_updated_v1
Document title modified
drive
drive.file.permission_member_added_v1
Document collaborator added
approval
approval_instance
Approval instance status changed
approval
approval_task
Approval task changed
vc
vc.meeting.meeting_started_v1
/
meeting_ended_v1
VC meeting started/ended
EventKey and EventType are usually consistent; the
header.event_type
in the received payload equals the
event_type
field.

Permissions and Open Platform Configuration

Default App Token, no
auth login
required

Event subscription uses App identity (app_id + app_secret), user token is not mandatory. Just configure
~/.feishu-cli/config.yaml
or set the
FEISHU_APP_ID
/
FEISHU_APP_SECRET
environment variables.

Two-step Configuration on Feishu Open Platform

In your application console at open.feishu.cn:
  1. 「Event Subscription - Receive Events via Long Connection」 Enable long connection mode (feishu-cli uses WebSocket, not webhook URL mode)
  2. 「Events and Callbacks - Event Subscription」 Select the target EventType (consistent with the Event Type output by
    event schema <key>
    ) and publish the version
  3. Scope activation: The scopes required for each EventKey can be found in the
    Scopes
    field of
    event schema <key>
    ; activate them on the "Permission Management" page. The
    event
    domain has been added to the
    --domain event --recommend
    recommendation list, allowing one-click application for the union of common scopes for IM/contact/calendar/drive/approval/vc

Common Errors

PhenomenonCauseSolution
WS connection failed, stderr reports ws errorLong connection mode not enabledEnable "Event Subscription - Receive Events via Long Connection" on Feishu Open Platform
Ready message appears after startup, but no events are receivedTarget EventType not checked in "Event Subscription" / version not publishedRe-check and publish the version
Events are received but payload fields are missingApp lacks corresponding scopes (e.g.,
im:message.p2p_msg:readonly
)
Check Scopes via
event schema <key>
, activate on the permission management page and re-subscribe
event consume
exits immediately with reason=error
Incorrect App ID/Secret / network unreachable / using lark domain but BaseURL set to feishuCheck
config.yaml
; for lark international version, use
--base-url https://open.larksuite.com
or corresponding configuration

Recommended Usage for AI Agent Background Subscription

Single EventKey Background Subscription (
run_in_background=true
)

python
# 1. Start consume in background, redirect stderr/stdout respectively
task = Bash(
    command='feishu-cli event consume im.message.receive_v1 --output-dir ./events 2> consume.log',
    run_in_background=True,
)

# 2. Tail consume.log and block until "[event] ready event_key=im.message.receive_v1"

# 3. Additional sleep 1-3s to complete WS handshake

# 4. Business logic: tail stdout / read ./events/*.json to process new events

# 5. Exit: feishu-cli event stop --event-key im.message.receive_v1
#         Or kill the background Bash task from parent process (SIGTERM triggers graceful shutdown + unregister)

Subprocess stdin EOF Protocol (Non-TTY)

In non-TTY mode, closing stdin triggers graceful exit (reason=signal). For Python
subprocess.Popen
with
stdin=subprocess.PIPE
,
p.stdin.close()
after processing is more stable than SIGTERM—the consume process will finish processing the current event before exiting.

Limit Single Run Duration / Event Count

Always use
--max-events N --timeout Ds
in debugging scenarios to avoid leaving background processes that consume API quota:
bash
feishu-cli event consume im.message.receive_v1 --max-events 1 --timeout 30s
# Capture 1 event demo / 30-second timeout double insurance

Pitfalls and Notes

  • Persistent daemon process:
    event consume
    runs blocking until signal/timeout/EOF; it will not exit on its own. When running in the background for AI Agent, you must configure
    --max-events
    /
    --timeout
    or explicitly run
    event stop
    , otherwise long-running processes will remain.
  • flock cross-process mutual exclusion: bus.json read/write operations all use flock, so registering multiple
    event consume
    processes simultaneously is safe; do not edit bus.json manually.
  • Automatic exit on pipe broken: If downstream jq / tee closes stdout (typical scenario:
    event consume ... | head -1
    ), SIGPIPE will be triggered, and consume will actively cancel exit with reason=signal, avoiding being stuck waiting for Ctrl-C.
  • --quiet
    does not affect ready marker
    : The ready marker uses real
    os.Stderr
    bypassing
    --quiet
    redirection, so even if
    --quiet
    is enabled, the parent process can still wait for the ready line; but other diagnostics (including
    [event] exited
    reason) will be suppressed.
  • AutoReconnect infinite retry: oapi-sdk-go v3 ws.Client defaults to
    WithAutoReconnect(true)
    , retries infinitely after disconnection (interval 2 minutes + initial jitter). For long-term disconnection scenarios, it is recommended to use
    --timeout
    to exit actively and be pulled up by the outer daemon process, which is more controllable than infinite inner retry.
  • status does not actively ping processes:
    event status
    uses
    signal(0)
    to check liveness, which may theoretically misjudge in PID reuse scenarios (very low probability).
    event stop --pid N
    also uses syscall.Kill, which will fail with ESRCH if the wrong PID is hit, avoiding accidental killing.
  • Independent file per event: In
    --output-dir
    mode, each event is persisted as
    <event_id>.json
    , a large number of small files may be created for high-frequency events in a short time; persistence is only for traceability, it is recommended to use stdout NDJSON for business consumption.
  • --jq
    only supports dot paths
    :
    --jq .event.message
    projects each event to the subtree before output; events that do not match will be skipped (no empty lines output). Always use pipe with external jq for complex filtering.
  • --output-dir
    only supports safe relative paths
    : Passing
    ~/events
    ,
    /tmp/events
    ,
    ../events
    will all report errors; use
    ./events
    or
    events/today
    .

When to Switch to Other Skills

TaskRoute
Send messages / replies / cards / notificationsfeishu-cli-msg
Construct interactive card JSONfeishu-cli-card
Process received message events → write to multidimensional tablesfeishu-cli-bitable (parse payload and call record command)
Process received approval events → query approval detailsfeishu-cli-toolkit (approval subcommand)
Query group information/members after receiving group messagesfeishu-cli-chat
Webhook URL mode (HTTP callback, non-long connection)Not within the scope of this skill; use "Request URL Configuration" on Feishu Open Platform + self-built HTTP server
Batch pull historical messages (non-real-time)
msg history
/
msg list
from feishu-cli-chat

References

Security — event_id Filename Sanitization

When
--output-dir
is enabled, each event is dumped as
<event_id>.json
. The v1 PR added
sanitizeEventID
for defense:
  • Only retains
    [A-Za-z0-9_-]
    characters, truncates to 128 characters in length
  • ..
    ,
    /
    , spaces, and special symbols are discarded
  • If sanitization results in an empty string → skip dumping (do not write a file with empty filename)
Defense scenario: When the server payload is abnormal or maliciously constructed (e.g.,
header.event_id = "../etc/passwd"
), writeFile will not escape from
--output-dir
.