diff --git a/README.md b/README.md
index 65b696fe..7ab90f65 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ demonstrate basic system features, integration APIs, and best practices.
| [Cancel GitHub Copilot access for inactive users](./github_copilot_seats/)
[](https://app.autokitteh.cloud/template?template-name=github_copilot_seats) | If Copilot was not used in a preceding period by users, unsubscribe and notify them in Slack. Users can ask for their subscription to be reinstated. | github, slack |
| [GitHub Marketplace to Slack](./github_marketplace_to_slack/)
[](https://app.autokitteh.cloud/template?template-name=github_marketplace_to_slack) | Forward GitHub Marketplace notifications to Slack | github, slack |
| [GitHub Review - PR Review Request Bot](./github_review/)
[](https://app.autokitteh.cloud/template?template-name=github_review) | Slash command bot for requesting and removing PR reviews via comments | github |
+| [GitHub Wait - PR/Issue State Management Bot](./github_wait/)
[](https://app.autokitteh.cloud/template?template-name=github_wait) | Slash command bot for managing waiting states on PRs and issues with automatic label updates | github |
| [Gocat - URL Shortener & Redirector](./gocat/)
[](https://app.autokitteh.cloud/template?template-name=gocat) | Simple URL shortener using Google Sheets as a data store with webhook-based redirection | googlesheets |
| [Google Calendar To Asana](./google_cal_to_asana/)
[](https://app.autokitteh.cloud/template?template-name=google_cal_to_asana) | Creates Asana tasks based on Google Calendar events | googlecalendar, asana |
| [Create Jira ticket from Google form](./google_forms_to_jira/)
[](https://app.autokitteh.cloud/template?template-name=google_forms_to_jira) | Create and update Jira tickets automatically from Google Forms responses | googleforms, jira |
diff --git a/github_wait/Makefile b/github_wait/Makefile
new file mode 100644
index 00000000..b990884a
--- /dev/null
+++ b/github_wait/Makefile
@@ -0,0 +1,20 @@
+UVX=uvx --with autokitteh --with PyGithub
+
+.PHONY: all
+all: lint typecheck format
+
+.PHONY: lint
+lint:
+ $(UVX) ruff check --config ../pyproject.toml --ignore I001
+
+.PHONY: format
+format:
+ $(UVX) ruff format --check --config ../pyproject.toml
+
+.PHONY: typecheck
+typecheck:
+ $(UVX) mypy --follow-untyped-imports .
+
+.PHONY: deploy
+deploy:
+ akl deploy --manifest autokitteh.yaml
diff --git a/github_wait/README.md b/github_wait/README.md
new file mode 100644
index 00000000..95576c10
--- /dev/null
+++ b/github_wait/README.md
@@ -0,0 +1,188 @@
+---
+title: GitHub Wait - PR/Issue State Management Bot
+description: Slash command bot for managing waiting states on PRs and issues with automatic label updates
+integrations: ["github"]
+categories: ["Productivity", "DevOps"]
+tags: ["github", "pull_requests", "issue_management", "slash_commands", "labels"]
+---
+
+# GitHub Wait - PR/Issue State Management Bot
+
+[](https://app.autokitteh.cloud/template?name=github_wait)
+
+A GitHub bot that helps reviewers track PRs that are waiting for author updates. Reviewers can mark PRs with `/wait` after completing their review, and the bot automatically removes the label when the author pushes new changes - making it easy to see at a glance which PRs are ready for re-review.
+
+## Features
+
+- **Quick PR Triage**: Reviewers can instantly see which PRs have been updated and need attention
+- **Automatic Label Removal**: Labels disappear when authors push new code - no manual cleanup needed
+- **Slash Command Interface**: Simple `/wait` or `/wait-any` commands in review comments
+- **Two Wait Modes**:
+ - `/wait` - removed only when author pushes code (ideal for "changes requested")
+ - `/wait-any` - removed when author comments or pushes code (for discussions)
+- **Visual Feedback**: Emoji reactions confirm command execution
+- **Works Everywhere**: Use in PR reviews, review comments, or regular comments
+
+## Why?
+
+Active reviewers often manage dozens of PRs simultaneously. The problem: **when looking at a PR list, it's hard to tell which PRs have been updated since your last review.**
+
+GitHub's native notifications don't give you an at-a-glance view of what needs attention. This bot solves that by:
+
+- **After reviewing**: Mark the PR with `/wait` to indicate you're waiting for the author
+- **Author updates**: When they push new code, the label automatically disappears
+- **Quick scanning**: Filter your PR list by the waiting label to see what needs re-review
+- **No manual cleanup**: Labels remove themselves - no stale markers cluttering your views
+
+For reviewers handling many PRs, this creates a self-maintaining "reviewed, waiting for author" queue that updates automatically as authors respond.
+
+### What [AutoKitteh](https://autokitteh.com) Provides
+
+- Event-driven GitHub integration
+- No server configuration needed
+- Built-in GitHub API client
+- Automatic event filtering
+- Production ready deployment
+
+## How It Works
+
+### Typical Reviewer Workflow
+
+1. **Review a PR**: Complete your code review and request changes
+2. **Mark as waiting**: Add `/wait` in your review comment
+3. **Bot adds label**: The `waiting` label appears on the PR
+4. **Author pushes code**: When they address your feedback and push commits
+5. **Label auto-removes**: The bot detects the push and removes the label
+6. **You see it's ready**: The PR disappears from your "waiting" filter - time to review again!
+
+### Wait Modes
+
+**`/wait` (waiting for code changes)** - Best for most reviews
+- Adds `waiting` label
+- Label removed only when author pushes new code
+- Ideal after requesting changes or when specifically waiting for code updates
+
+**`/wait-any` (waiting for any response)** - For discussions
+- Adds `waiting:any` label
+- Label removed when author comments OR pushes code
+- Use when you need clarification or are waiting for discussion to continue
+
+## Usage
+
+### After Completing a Review
+
+When you finish reviewing a PR and are waiting for the author to make changes:
+
+```
+/wait
+```
+
+This adds the `waiting` label to the PR. When the author pushes new code, the label automatically disappears.
+
+**Pro tip**: Filter your PR list by `label:waiting` to see all PRs you've reviewed that are still waiting for author updates. When the label disappears, you know it's time to re-review!
+
+### For Discussion-Based Reviews
+
+When you're waiting for the author to respond to questions or clarify something:
+
+```
+/wait-any
+```
+
+This adds the `waiting:any` label. It will be removed when the author either comments or pushes code.
+
+### Switching Wait Modes
+
+You can change your mind by using the other command - the bot automatically switches labels:
+
+```
+/wait # Adds "waiting" label
+/wait-any # Switches to "waiting:any" label
+```
+
+## Deployment
+
+Deploy using the AutoKitteh CLI or web interface:
+
+[](https://app.autokitteh.cloud/template?name=github_wait)
+
+### Command Line Deployment
+
+1. **Install the CLI**: https://docs.autokitteh.com/get_started/install
+2. **Authenticate**: `ak auth login`
+3. **Deploy**: `make deploy`
+4. **Initialize connection**: Log in to https://autokitteh.cloud and initialize the GitHub connection
+5. **Configure repository**: Set up the connection to watch your GitHub repository
+
+## Connections
+
+- **github** (required): GitHub integration for receiving events and managing labels
+
+## Implementation Details
+
+### Core Files
+
+- **`handlers.py`** (`handlers.py:23`): Main event handlers
+ - `on_command()`: Processes `/wait` and `/wait-any` commands
+ - `on_pull_request_sync()`: Removes waiting labels when new code is pushed
+ - `on_issue_comment()`: Removes `waiting:any` label on new issue comments
+ - `on_pull_request_review_comment()`: Removes `waiting:any` label on new PR review comments
+ - `on_pull_request_review()`: Removes `waiting:any` label on new PR reviews
+ - `_label()`: Helper function for adding and removing labels
+
+### How It Uses AutoKitteh
+
+The system uses AutoKitteh's event-driven architecture:
+
+```python
+from autokitteh import Event
+from autokitteh.github import github_client
+```
+
+1. **Event Triggers** (`autokitteh.yaml:10`): Multiple GitHub event types
+
+ ```yaml
+ triggers:
+ - name: command
+ connection: github
+ event_type: slash_command
+ call: handlers.py:on_command
+ filter: "data.command.name in ['wait', 'wait-any'] && (data.actual_event_type in ['issue_comment', 'pull_request_review_comment', 'pull_request_review'])"
+ ```
+
+2. **Event Filtering**: Smart filtering based on event types and slash commands
+
+3. **GitHub API Integration** (`handlers.py:10`): Built-in GitHub client
+ ```python
+ github = github_client("github")
+ repo = github.get_repo(data.repository.full_name)
+ issue = repo.get_issue(number=data.issue.number)
+ issue.add_to_labels("waiting")
+ ```
+
+### Labels Used
+
+The bot uses two labels:
+
+- **`waiting`**: Indicates waiting for code changes (push events only)
+- **`waiting:any`**: Indicates waiting for any activity (comments or code changes)
+
+These labels should be created in your repository. The bot will create them automatically on first use if they don't exist.
+
+### Smart Activity Detection
+
+The bot intelligently handles different scenarios to prevent false positives:
+
+- **Reviewer Comments**: When reviewers add more comments (including wait commands), labels stay in place
+- **Author Comments**: Comments from the author remove the `waiting:any` label (but not `waiting`)
+- **Code Pushes**: New commits from the author remove both `waiting` and `waiting:any` labels
+- **Reviews**: New review submissions remove the `waiting:any` label
+
+This means reviewers can continue discussing without accidentally removing the waiting state - only author activity triggers label removal.
+
+### Bot Responses
+
+The bot provides feedback through:
+
+- **Emoji reactions**: `+1` emoji on command comments to confirm execution
+- **Automatic label management**: Labels appear and disappear based on activity
diff --git a/github_wait/autokitteh.yaml b/github_wait/autokitteh.yaml
new file mode 100644
index 00000000..88cd2beb
--- /dev/null
+++ b/github_wait/autokitteh.yaml
@@ -0,0 +1,44 @@
+version: v2
+
+project:
+ name: github_wait
+
+ connections:
+ - name: github
+ integration: github
+
+ triggers:
+ - # On slash command /wait or /wait-any, add appropriate labels.
+ name: command
+ connection: github
+ event_type: slash_command
+ call: handlers.py:on_command
+ filter: "data.command.name in ['wait', 'wait-any'] && (data.actual_event_type in ['issue_comment', 'pull_request_review_comment', 'pull_request_review'])"
+
+ - # On a new issue comment, remove wait-any labels unless the comment is a command.
+ name: issue_comment
+ connection: github
+ event_type: issue_comment
+ call: handlers.py:on_issue_comment
+ filter: "data.action == 'created'"
+
+ - # On a new review comment, remove wait-any labels unless the comment is a command.
+ name: review_comment
+ connection: github
+ event_type: pull_request_review_comment
+ call: handlers.py:on_pull_request_review_comment
+ filter: "data.action == 'created'"
+
+ - # On a new review , remove wait-any labels unless the comment is a command.
+ name: review_submitted
+ connection: github
+ event_type: pull_request_review
+ call: handlers.py:on_pull_request_review
+ filter: "data.action == 'submitted'"
+
+ - # On PR sync, remove all waiting labels.
+ name: pull_request
+ connection: github
+ event_type: pull_request
+ call: handlers.py:on_pull_request_sync
+ filter: "data.action == 'synchronize'"
diff --git a/github_wait/handlers.py b/github_wait/handlers.py
new file mode 100644
index 00000000..e6b51464
--- /dev/null
+++ b/github_wait/handlers.py
@@ -0,0 +1,140 @@
+"""Handlers for github issue bot"""
+
+from github import Issue
+from github import PullRequest
+
+from autokitteh import Event
+from autokitteh.github import github_client
+
+
+github = github_client("github")
+
+
+# waiting only for push.
+_waiting_push_label = "waiting"
+
+# waiting for either push or comment.
+_waiting_any_label = "waiting:any"
+
+_IssueOrPR = Issue.Issue | PullRequest.PullRequest
+
+
+# Can be called on either issue comments, pr review comments, or pr review submitted.
+def on_command(event: Event) -> None:
+ match event.data.command.name:
+ case "wait":
+ add, remove = _waiting_push_label, _waiting_any_label
+ case "wait-any":
+ add, remove = _waiting_any_label, _waiting_push_label
+ case _:
+ print("unhandled command")
+ return
+
+ data = event.data.actual_data
+
+ repo = github.get_repo(data.repository.full_name)
+
+ if data.get("issue"):
+ issue: _IssueOrPR = repo.get_issue(number=data.issue.number)
+ elif data.get("pull_request"):
+ issue = repo.get_pull(number=data.pull_request.number)
+
+ _label(issue, adds=[add], removes=[remove])
+
+ if data.get("comment"):
+ issue.get_comment(data.comment.id).create_reaction("+1")
+
+
+def on_pull_request_sync(event: Event) -> None:
+ data = event.data
+
+ repo = github.get_repo(data.repository.full_name)
+ pr = repo.get_pull(data.number)
+
+ _label(pr, adds=[], removes=[_waiting_push_label, _waiting_any_label])
+
+
+def on_issue_comment(event: Event) -> None:
+ data = event.data
+
+ if not any(
+ label["name"] in [_waiting_any_label, _waiting_push_label]
+ for label in data.issue.labels
+ ):
+ print("not waiting, ignored")
+ return
+
+ if data.slash_commands and any(
+ cmd["name"] in ["wait", "wait-any"] for cmd in data.slash_commands
+ ):
+ # Don't remove labels if this comment is a command.
+ print("comment contains wait command, ignored")
+ return
+
+ repo = github.get_repo(data.repository.full_name)
+ issue = repo.get_issue(data.issue.number)
+
+ # Remove only wait-any label on new comment.
+ _label(issue, adds=[], removes=[_waiting_any_label])
+
+
+def on_pull_request_review_comment(event: Event) -> None:
+ data = event.data
+
+ if not any(
+ label["name"] in [_waiting_any_label, _waiting_push_label]
+ for label in data.pull_request.labels
+ ):
+ print("not waiting, ignored")
+ return
+
+ if data.slash_commands and any(
+ cmd["name"] in ["wait", "wait-any"] for cmd in data.slash_commands
+ ):
+ # Don't remove labels if this comment is a command.
+ print("comment contains wait command, ignored")
+ return
+
+ repo = github.get_repo(data.repository.full_name)
+ pr = repo.get_pull(data.pull_request.number)
+
+ # Remove only wait-any label on new comment.
+ _label(pr, adds=[], removes=[_waiting_any_label])
+
+
+def on_pull_request_review(event: Event) -> None:
+ data = event.data
+
+ if not any(
+ label["name"] in [_waiting_any_label, _waiting_push_label]
+ for label in data.pull_request.labels
+ ):
+ print("not waiting, ignored")
+ return
+
+ if data.slash_commands and any(
+ cmd["name"] in ["wait", "wait-any"] for cmd in data.slash_commands
+ ):
+ # Don't remove labels if this comment is a command.
+ print("comment contains wait command, ignored")
+ return
+
+ repo = github.get_repo(data.repository.full_name)
+ pr = repo.get_pull(data.pull_request.number)
+
+ # Remove only wait-any label on new comment.
+ _label(pr, adds=[], removes=[_waiting_any_label])
+
+
+def _label(issue: _IssueOrPR, adds: list[str], removes: list[str]) -> None:
+ labels = {label.name for label in issue.get_labels()}
+
+ for remove in removes:
+ if remove in labels:
+ print(f"removing label {remove}")
+ issue.remove_from_labels(remove)
+
+ for add in adds:
+ if add not in labels:
+ print(f"adding label {add}")
+ issue.add_to_labels(add)