Control Kōan directly from GitHub PR and issue comments using @mention commands.
Introduced in: PR #251 — 10 commits, 6 new modules, 102 tests.
Instead of switching to Telegram to tell Kōan to rebase a PR or review an issue, you can post a comment on the PR/issue itself:
@koan-bot rebase
Kōan polls GitHub notifications, detects the @mention, validates the command and the user's permissions, reacts with 👍 to acknowledge, and queues a mission — all without webhooks or external services.
In instance/config.yaml:
github:
nickname: "koan-bot" # Your bot's GitHub username (required)
commands_enabled: true # Master switch
authorized_users: ["*"] # "*" = anyone with write access, or ["alice", "bob"]
max_age_hours: 24 # Ignore notifications older than this (default: 24)Kōan uses the gh CLI for all GitHub API calls. Verify it works:
gh auth status
gh api notifications --paginate | head@koan-bot rebase
Kōan will:
- React with 👍 on the comment (acknowledgment)
- Create a pending mission:
- [project:myapp] /rebase https://github.com/owner/repo/pull/42 - Execute it in the next agent loop iteration
Any skill with github_enabled: true in its SKILL.md can be triggered via @mentions. Currently 16 commands are available:
| Command | Aliases | What it does | Context-aware |
|---|---|---|---|
ask |
— | Ask Koan a question about a PR or issue | Yes |
audit |
— | Audit a project codebase and create issues for findings | Yes |
brainstorm |
— | Decompose a topic into linked GitHub issues | Yes |
deepplan |
deeplan |
Spec-first design with Socratic exploration | Yes |
fix |
— | Fix a GitHub issue end-to-end, or batch-queue all open issues | Yes |
gh_request |
— | Natural-language GitHub request dispatch | Yes |
implement |
impl |
Implement a GitHub issue | Yes |
plan |
— | Deep-think and create a structured plan | Yes |
profile |
perf, benchmark |
Queue a performance profiling mission | Yes |
rebase |
rb |
Rebase a PR onto latest upstream | Yes |
recreate |
rc |
Recreate a diverged PR from scratch | Yes |
refactor |
rf |
Queue a refactoring mission | Yes |
review |
rv |
Queue a code review for a PR or issue | Yes |
reviewrebase |
rr |
Review then rebase combo for a PR | Yes |
security_audit |
security, secu |
Security-focused audit of a codebase | Yes |
squash |
sq |
Squash all PR commits into one clean commit | Yes |
Some commands accept additional context after the command word. For example:
@koan-bot implement phase 1 only
This creates a mission: /implement https://github.com/owner/repo/issues/42 phase 1 only
Only skills with github_context_aware: true in their SKILL.md receive the extra context. For other commands, trailing text is ignored.
If the context contains a GitHub URL, it overrides the default subject URL from the notification:
@koan-bot implement https://github.com/owner/other-repo/issues/99 phase 2
github:
nickname: "koan-bot" # Bot's GitHub @mention name (required if enabled)
commands_enabled: false # Master switch (default: false)
authorized_users: ["*"] # Allowlist: "*" for all with write access, or explicit usernames
max_age_hours: 24 # Stale notification threshold (default: 24 hours)nickname: The GitHub username Kōan uses. Must match the account behindGH_TOKEN. This is the@nameusers will mention.commands_enabled: Feature toggle. Whenfalse, notification polling is completely skipped.authorized_users: Controls who can trigger commands. Even with["*"], Kōan always verifies the user has write access to the repository via the GitHub API. This prevents drive-by command injection from random commenters.max_age_hours: Notifications older than this are silently discarded. Protects against processing a backlog of stale mentions after downtime.
When reply_enabled: true, Kōan responds to non-command @mentions with AI-generated replies. Two additional settings control who can trigger replies and how often:
github:
reply_enabled: true
reply_authorized_users: ["*"] # Who can trigger AI replies (default: uses authorized_users)
reply_rate_limit: 5 # Max replies per user per hour (default: 5, min: 1)reply_authorized_users: Separate from commandauthorized_users— allows a broader audience for read-only replies without granting command execution.["*"]means anyone can trigger replies (no permission check at all, unlike command wildcard which still checks GitHub write access). Omit to fall back toauthorized_users. Set[]to disable replies entirely.reply_rate_limit: Prevents API quota abuse when replies are open broadly. Tracks per-user reply counts over a rolling 1-hour window. Default: 5, minimum: 1.
Override authorized_users and reply_authorized_users for specific repositories:
projects:
sensitive-repo:
path: "/path/to/sensitive-repo"
github:
authorized_users: ["alice", "bob"] # Only these users, not the global wildcard
reply_authorized_users: ["*"] # But allow AI replies for anyoneThis is useful when the global config allows ["*"] but a specific repo needs tighter control for commands, or vice versa for replies.
| Variable | Purpose |
|---|---|
GH_TOKEN |
GitHub authentication for the gh CLI (required) |
GITHUB_USER |
Override bot username for API calls (optional, falls back to github.nickname) |
The feature spans 6 modules in koan/app/:
loop_manager.py ← Polls during sleep cycle (throttled)
↓
github_notifications.py ← Fetches & filters notifications, parses @mentions
↓
github_command_handler.py ← Validates commands, checks permissions, creates missions
↓
github_config.py ← Reads config.yaml / projects.yaml settings
↓
github_skill_helpers.py ← Shared URL extraction, project resolution, mission queuing
↓
skills.py ← Skill flags: github_enabled, github_context_aware
1. Sleep cycle tick → process_github_notifications()
2. Fetch unread notifications (reason: "mention", filtered to known repos)
3. For each notification:
a. Skip if stale (> max_age_hours)
b. Fetch triggering comment
c. Skip if self-mention (bot's own comments)
d. Check in-memory + reaction-based deduplication
e. Parse @mention → extract (command, context)
f. Validate command → skill must have github_enabled: true
g. Check user permission → allowlist + GitHub write access
h. Insert mission into missions.md (BEFORE reacting — crash-safe)
i. React with 👍 on comment (marks as processed)
j. Mark notification thread as read
Two-tier approach to prevent duplicate missions:
- In-memory set:
_processed_commentstracks comment IDs within a session. Fast, but lost on restart. - GitHub 👍 reaction: Persistent marker. On restart, Kōan checks if it already reacted before processing.
The mission is inserted before the reaction is added. If Kōan crashes between these two steps, the worst case is a duplicate mission — never a lost command.
Notifications are checked during the agent's interruptible sleep cycle, with exponential backoff:
| Condition | Check interval |
|---|---|
| Notifications found | 60 seconds (base) |
| 1 empty check | 120 seconds |
| 2 consecutive empty | 240 seconds |
| 3+ consecutive empty | 300 seconds (cap) |
Backoff resets immediately when any notification is found. This reduces unnecessary API calls during quiet periods while maintaining fast response when activity resumes.
When a command fails validation (unknown command, permission denied), Kōan:
- Posts an error reply on the GitHub comment thread (❌ with explanation)
- Includes the list of available commands for "unknown command" errors
- Deduplicates error replies to avoid spam
@mentions inside code blocks are ignored:
Here's an example:
```
@koan-bot rebase ← This is NOT processed
```
@koan-bot rebase ← This IS processedAny skill can opt into GitHub @mention triggering by adding flags to its SKILL.md:
---
name: my-skill
github_enabled: true # Allow triggering via @mentions (also enables Jira)
github_context_aware: true # Pass extra text as context (optional)
group: integrations # Groups the skill under "Integrations" in help
commands:
- name: my-command
description: "Does something useful"
handler: handler.py
---The skill's handler receives the same SkillContext whether triggered from Telegram, GitHub, or Jira. The mission format for core skills is /my-command <url> [context].
Skills under instance/skills/<scope>/ with a handler.py follow a shorter path: the GitHub/Jira bridges call execute_skill(skill, ctx) directly at notification time — the same entry point Telegram uses — instead of queueing a slash mission that has no registered runner in skill_dispatch._SKILL_RUNNERS. This keeps custom skills self-contained: the handler can queue whatever mission it needs via insert_pending_mission.
The helper is app.external_skill_dispatch.try_dispatch_custom_handler. It also auto-feeds a Jira key into ctx.args when the author omitted one:
- Jira source: the issue the comment is on.
- GitHub source: the first
FOO-123-style key found in the issue title, then body. - If the author already typed a key (e.g.
@bot cpfix CPANEL-1), it's passed through verbatim.
Non-core skills should set group: integrations so they render in a dedicated Integrations section at the bottom of @bot help, separate from the core command groups (code, pr, missions, …).
See koan/skills/README.md for the full skill authoring guide.
Every command goes through two gates:
- Allowlist check: User must be in
authorized_users(or wildcard*is set) - Write access verification: Even with wildcard auth, Kōan always calls the GitHub API to verify the user has
writeoradminpermission on the repository
This means a random person commenting @koan-bot rebase on a public repo will be rejected — they need actual write access, not just the ability to comment.
Notifications older than max_age_hours (default: 24h) are silently discarded and marked as read. This prevents processing an accumulated backlog after extended downtime.
Comments posted by the bot itself are always ignored, preventing infinite loops.
The mission is written to missions.md before the 👍 reaction is added. This guarantees:
- No lost commands: If Kōan crashes after writing the mission but before reacting, the mission persists. On restart, it will re-process the notification but find the mission already exists.
- At-most-once reaction: The reaction serves as a durable "processed" marker.
- Check feature is enabled:
commands_enabled: truein config.yaml - Verify nickname matches:
github.nicknamemust match the GitHub account behindGH_TOKEN - Check notification visibility:
gh api notifications --paginateshould show the mention - Check logs:
make logs— look forGitHub:log entries - Verify write access: The commenting user needs write/admin permission on the repo
The 👍 means Kōan acknowledged the command and created a mission. Check:
instance/missions.md— the mission should be in the Pending section- Agent loop logs — the mission will be picked up in the next iteration
The repo must be configured in projects.yaml with a valid path. Kōan resolves the notification's repository against known projects. If there's no match, it can't determine where to execute.
Expected behavior when Kōan was interrupted between mission creation and reaction. The duplicate will be harmless — the agent detects already-completed missions.
GitHub and Jira integrations can run simultaneously. Both dispatch the same set of commands (any skill with github_enabled: true) but serve different roles:
- GitHub: Code-centric actions — PR rebases, code reviews, issue implementation with direct diff access.
- Jira: Project-level planning — feature planning, audits, and implementation from Jira tickets.
Missions from GitHub are marked with 📬, missions from Jira with 🎫. Both enter the same mission queue.
See Jira Integration for full setup instructions and the combined configuration guide.
- Jira Integration — Jira @mention integration (complementary)
- Skills README — Skill authoring guide with
github_enabledflag documentation - Messaging: Telegram — Alternative command interface via Telegram
- Messaging: Slack — Alternative command interface via Slack
- PR #251 — Original implementation
- Issue #243 — Feature request and design plan