-
Notifications
You must be signed in to change notification settings - Fork 17
GitHub Notification Routing
By default, all GitHub event notifications (pushes, PRs, issues, comments, reviews) land in your direct message with Kai. During active development, this buries your interactive conversation in noise.
The GITHUB_NOTIFY_CHAT_ID setting routes all GitHub notifications to a separate Telegram group, keeping your DM clean for actual conversation.
When GITHUB_NOTIFY_CHAT_ID is set, all GitHub event notifications go to the configured group:
| Event | Normal notification | Agent output |
|---|---|---|
| Push | Commit summary with branch and author | - |
| Pull request | Open/close/merge/reopen summary | PR review agent posts its review summary here |
| Issues | Open/close/reopen summary | Issue triage agent posts its triage summary here |
| Issue comments | Comment text with author and link | - |
| PR reviews | Review submitted/approved/changes requested | - |
Both the standard event formatters and the agent outputs (PR review, issue triage) route to the same group. There is no way to split them - it is all or nothing.
When GITHUB_NOTIFY_CHAT_ID is not set, everything goes to the user's DM as before. Zero behavior change.
Only GitHub events are rerouted. Everything else stays in your direct conversation:
- Interactive chat messages
- Scheduled job output (reminders, Claude jobs)
- Generic webhook notifications (
/webhookendpoint) - Voice message responses
- File/photo responses
Open Telegram and create a new group:
- Tap the compose/pencil icon (bottom-right on mobile, top-left on desktop)
- Select New Group
-
Add your Kai bot as a member - search by its
@username - Name it something descriptive (e.g., "Kai GitHub", "Dev Notifications")
- Tap Create
The bot needs to be a member of the group to send messages there. You can make the group private - the bot does not need admin privileges to send messages.
Telegram groups have negative integer chat IDs (e.g., -5241088228). Individual users have positive IDs.
Option A: Send a message and check the logs
- Send
/startin the new group (bot commands are always visible to bots, even with privacy mode) - Check Kai's logs for the chat ID:
# Production grep "chat_id" /var/lib/kai/logs/kai.log | tail -5 # Development grep "chat_id" logs/kai.log | tail -5
Option B: Temporarily use getUpdates
If the webhook is consuming updates so they don't appear in getUpdates, you can temporarily disable the webhook, send a message, poll for it, then restore the webhook:
# Remove webhook temporarily
curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/deleteWebhook"
# Send a message in the group, then:
curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getUpdates" | python3 -m json.tool
# Look for "chat": {"id": -XXXXXXXXXX, "title": "Your Group Name"}
# Then restart Kai to re-register the webhookOption C: Use @userinfobot (may have duplicates)
Add @userinfobot to the group. It replies with the chat ID immediately. Note that there may be multiple bots with similar names - look for the verified one.
Interactive installer:
make configThe installer prompts for "GitHub notification chat ID" under the GitHub notifications section. Paste the negative integer (including the dash).
Manual configuration:
Add to your .env (development) or /etc/kai/env (production):
GITHUB_NOTIFY_CHAT_ID=-5241088228The value must be a valid integer. Empty or missing means the feature is disabled.
# Development
make run
# Production (launchd will auto-restart)
kill $(ps aux | grep 'python.*-m kai' | grep -v grep | awk '{print $2}')On startup, the webhook server parses GITHUB_NOTIFY_CHAT_ID and stores it in the app dict. If the value is invalid (not an integer), a warning is logged and the feature is treated as disabled.
Push a commit, open an issue, or comment on a PR in a repo with a GitHub webhook configured. The notification should appear in the group instead of your DM.
Kai only delivers GitHub webhook events to users who are subscribed to the relevant repo. Subscriptions are defined by the github_repos field in users.yaml (the admin-set baseline) and can be extended or trimmed at runtime via Telegram commands.
/github add owner/repo
Subscribes you to webhook events from owner/repo. If you have a GitHub token stored (see below), Kai registers the webhook on the repo automatically. If not, it gives you the webhook URL and a cURL command to register it manually.
/github remove owner/repo
Unsubscribes you from owner/repo. If no other users are subscribed to that repo and you have a token stored, Kai removes the webhook from GitHub automatically.
/github
Shows your current effective subscriptions with source attribution - each repo is labeled (users.yaml) or (added via /github add). Repos removed via /github remove are excluded from the list even if they appear in users.yaml.
Your effective subscription list is computed as:
(yaml_repos ∪ db_added) - db_removed
The yaml baseline (set by the admin in users.yaml) is never modified. User additions and removals are stored as separate delta lists in the database, keyed per chat ID. Admin changes to users.yaml propagate automatically - a repo added to users.yaml appears in your effective list unless you have explicitly removed it via /github remove.
A personal access token lets Kai register and remove webhooks on your behalf automatically, without requiring you to visit the GitHub web UI.
Store a token:
/github token ghp_xxxxxxxxxxxxxxxxxxxx
The token is stored in the local SQLite database. Its value is never echoed back or logged.
Remove a token:
/github token clear
Token permissions required:
The token needs the admin:repo_hook scope (or write:repo_hook if you don't need delete). For organization repos, the token owner must have admin access to the repo.
Without a token:
/github add still works - it subscribes you and prints the manual webhook registration instructions. You add the webhook on GitHub yourself, pointing to https://your-domain/webhook/github with your WEBHOOK_SECRET.
The routing happens early in the GitHub event handler (webhook.py:_process_github_event), before any event-specific logic:
- Check if
GITHUB_NOTIFY_CHAT_IDis configured - If yes: set
chat_idandnotify_chat_idto the group ID - If no: fall through to per-user resolution (match GitHub login to Telegram user, or default to admin DM)
The chat_id variable flows to:
-
Standard notifications -
bot.send_message(chat_id, ...)for push/PR/issue/comment/review formatters -
PR review agent -
notify_chat_idpassed toreview.review_pr(), which includes it in thePOST /api/send-messagebody -
Issue triage agent -
notify_chat_idpassed totriage.triage_issue(), same pattern
The group chat ID is also added to allowed_user_ids in the app dict so that _resolve_chat_id() accepts it when the review and triage agents POST to /api/send-message.
Each user's notification destination is resolved from a four-layer stack (highest priority first):
-
Database - set via
/github notify <chat_id>in Telegram -
github_notify_chat_idinusers.yaml- per-user static default -
GITHUB_NOTIFY_CHAT_IDenv var - global fallback - The user's own DM - if none of the above are configured
This means you can route different users' notifications to different groups by setting github_notify_chat_id per user in users.yaml. The global GITHUB_NOTIFY_CHAT_ID env var acts as a fallback for any user without a per-user setting.
For example: alice has github_notify_chat_id: -100111111111 in her entry (routes to the eng team group), bob has no setting (falls back to GITHUB_NOTIFY_CHAT_ID, or his DM if that is also unset).
Each user's effective repo list is computed from:
-
github_reposinusers.yaml- admin-set baseline -
/github addadditions - stored in the database per user, layered on top of the baseline -
/github removeremovals - stored separately, subtracted from the union above
Formula: (yaml_repos ∪ db_added) - db_removed. Admin changes to users.yaml propagate automatically unless overridden by a user's explicit remove.
Admins with an empty github_repos list receive events from all repos as a wildcard fallback. Regular users only receive events for repos in their effective list.
| Command | Effect |
|---|---|
/github |
Show current settings - notification destination, repo subscriptions, feature toggles, token status |
/github notify <chat_id> |
Route your GitHub notifications to a different chat |
/github notify reset |
Restore notifications to your DM |
/github add <owner/repo> |
Subscribe to a repo (registers webhook if token is stored) |
/github remove <owner/repo> |
Unsubscribe from a repo (removes webhook if no other subscribers) |
/github token <value> |
Store a GitHub personal access token for automatic webhook management |
/github token clear |
Remove stored token |
/github reviews on|off |
Enable or disable the PR review agent for your account |
/github triage on|off |
Enable or disable the issue triage agent for your account |
Remove or comment out GITHUB_NOTIFY_CHAT_ID from your .env / /etc/kai/env and restart. Notifications revert to the DM immediately. You can leave the Telegram group in place for when you want to re-enable it.
Notifications still going to DM:
- Check that the env var is set and the service was restarted
- Check the logs for
Invalid GITHUB_NOTIFY_CHAT_IDwarnings - Verify the value is a valid integer (including the leading dash)
Bot can't send to the group:
- Make sure the bot is still a member of the group
- If the group was deleted or the bot was removed, send failures are logged but don't crash the service - the notification is simply lost
No notifications at all:
- This feature only affects GitHub notifications. Confirm your GitHub webhook is working by checking
gh api repos/OWNER/REPO/hooksand the recent deliveries on the GitHub webhook settings page - Check that Kai's webhook server is reachable (see Exposing Kai to the Internet)
Group chat ID looks wrong:
- Group IDs are always negative integers. If yours is positive, it is a user ID, not a group ID
- Supergroups (large groups, or groups upgraded by Telegram) may have different IDs than regular groups. Use whatever ID Telegram returns - both formats work