Lifecycle Hooks

botmux can asynchronously invoke external commands when key lifecycle events occur. If a command fails, times out, or doesn't exist, it only writes to the log and never blocks botmux's main flow.

Configuration Location

In order of precedence (highest to lowest):

  1. The BOTMUX_HOOKS_JSON environment variable (pass a JSON array directly)
  2. The file path specified by BOTMUX_HOOKS_FILE
  3. The default ~/.botmux/data/hooks.json

Quick Check: Write to a Local Log

The repo ships an example script you can copy and use right away:

chmod +x examples/hooks/echo-to-log.sh
HOOK_CMD="$(pwd)/examples/hooks/echo-to-log.sh"
mkdir -p ~/.botmux/data
cat > ~/.botmux/data/hooks.json <<JSON
[
  {
    "event": "session.requires_attention",
    "command": "$HOOK_CMD",
    "timeoutMs": 5000
  }
]
JSON

tail -f /tmp/botmux-hook.log

After any hook event fires, you'll see the JSON payload in the log. examples/hooks/ also includes examples for macOS Notification Center (osascript-notify.sh) and HTTP webhooks (http-webhook.sh).

Configuration Fields

[
  {
    "event": "session.requires_attention",
    "command": "/absolute/path/to/your-hook --flag value",
    "timeoutMs": 5000,
    "filter": { "chatId": "oc_xxx" },
    "redact": { "fullContentEvents": ["session.requires_attention"] }
  }
]
FieldTypeDescription
eventstringRequired. The event name to subscribe to (see table below)
commandstringRequired. The external executable command; supports arguments, but is not run through a shell
timeoutMsnumberOptional. Defaults to 5000; on timeout, sends SIGTERM first, then falls back to SIGKILL
filter.chatIdstring|string[]Optional. Only match the chat of the specified Lark group / topic
filter.senderOpenIdstring|string[]Optional. Only match the specified sender open_id
redact.fullContentEventsstring[]Optional. Long text is truncated by default; events in this allowlist pass through the full text

Supported Events

EventTrigger
topic.newA new topic / @mention is received
thread.replyA reply to an existing topic is received
outbound.sendbotmux successfully sends a regular message
outbound.replybotmux successfully replies to a topic message
schedule.firedA scheduled task finishes running
session.startA worker / adopt worker starts successfully
session.exitA worker exits, crashes, or the session is closed (silenced by default on daemon shutdown)
session.idleA session enters or leaves idle, deduplicated per session + state over 10s
session.requires_attentionA TUI prompt or a worker user_notify needs the user to act

Payload Fields

Every payload is written to the hook command via stdin, and the environment variable BOTMUX_HOOK_EVENT is also set. Each payload includes event and emittedAt; the event context may include sessionId, chatId, chatType, larkAppId, scope, anchor, title, cliId, workingDir, hasHistory, spawnedAt, and lastMessageAt.

Different events carry extra fields:

EventExtra fields
topic.newmessageId, senderOpenId, senderType, msgType, content
thread.replymessageId, rootId, parentId, senderOpenId, senderType, msgType, content
outbound.sendmessageId, msgType, uuid, content
outbound.replymessageId, replyId, msgType, replyInThread, uuid, content
schedule.firedid, name, schedule, status, error, rootMessageId, runAt
session.startreason, pid, adoptedFrom
session.exitreason, code (worker exit path; null for dashboard_close)
session.idleprevState, newState, transition, source
session.requires_attentionreason, description, optionsCount, optionsPreview, multiSelect, message

By default, content, message, description, finalOutput, and lastScreenContent are truncated to 600 characters, with xxxLength / xxxTruncated added; only events in redact.fullContentEvents pass through the full text.

Practical: Auto-Update Skills with session.start

botmux natively integrates agentbuddy as a skill source (botmux skills install <agentbuddy-command> to install, botmux skills update <name> to update). Combined with the session.start hook, you can automatically check for and update installed skills on every new session — equivalent to the SessionStart Hook in Relay / Claude Code's settings.json.

Update a Single Skill

[
  {
    "event": "session.start",
    "command": "botmux skills update my-skill-name",
    "timeoutMs": 60000
  }
]

Update All Installed Skills

botmux skills update accepts only a single skill name — no * or regex. To update everything, loop in a script:

#!/bin/bash
# ~/bin/botmux-update-all-skills.sh
botmux skills list | cut -f1 | while read -r name; do
  [ -n "$name" ] && botmux skills update "$name"
done
[
  {
    "event": "session.start",
    "command": "/root/bin/botmux-update-all-skills.sh",
    "timeoutMs": 120000
  }
]

Call agentbuddy CLI Directly to Update Global Skills

If you prefer running npx agentbuddy update directly (updating the user's global skills rather than botmux-managed skills), be aware of botmux's hook execution constraints: shell: false (no redirection or piping) and a scrubbed environment (only PATH/HOME/TMPDIR/SHELL/USER and a few other basics are preserved). Use a wrapper script:

#!/bin/bash
# ~/bin/agentbuddy-update.sh
export npm_config_registry="https://your-registry.example.com"  # if you use a private npm registry
npx -y agentbuddy update -y 2>/dev/null
[
  {
    "event": "session.start",
    "command": "/root/bin/agentbuddy-update.sh",
    "timeoutMs": 120000
  }
]

Notes

  • Timeout: The default timeoutMs is 5000ms. agentbuddy update involves network requests and typically takes longer — set it explicitly (60s+ recommended). On timeout, botmux sends SIGTERM first, then SIGKILL to the entire process group.
  • Fire-and-forget: Hooks run asynchronously and never block session startup; updated skills take effect in the next session.
  • Filter: Use filter to limit updates to specific chatId or senderOpenId, avoiding unnecessary updates for every session.
  • Recommended approach: Prefer botmux skills update (first approach) — it goes through botmux's telemetry scrubbing (clearAgentbuddyTelemetry) and updates the skill versions botmux injects, staying consistent with botmux's skill lifecycle.

Writing Your Own Hook

A hook command can be any executable: a bash / Python / Node / Go binary, an internal company CLI, or an HTTP forwarder. A command that does exit 0 is treated as a success; non-zero exits / timeouts / missing commands only write to the botmux log and never affect message send/receive, scheduled tasks, or the session lifecycle.