_private/qwestly-docs/Engineering/qwestly-bot.md
Table of Contents
Qwestly Bot (GitHub App)
qwestly is a GitHub App that lets developers run gh CLI commands as a
bot identity — PR reviews, comments, and issue interactions appear posted by
qwestly[bot] instead of a personal user.
Why
- Automated PR reviews from CI/CD (GitHub Actions) can use the bot
- Local PR reviews from AI tools can appear under the bot instead of your personal account, keeping human vs. automated contributions distinct
- Shared bot identity — any team member with the env vars can post as the bot
App details
| Field | Value |
|---|---|
| App name | qwestly |
| App ID | 3884618 |
| Owner | @Qwestly |
| Settings | https://github.com/organizations/Qwestly/settings/apps/qwestly |
| Installed on | Qwestly org (all repos) |
Permissions
| Permission | Level |
|---|---|
| Pull requests | Read & Write |
| Issues | Read & Write |
Added at App permissions.
Activity
In the past 2 days (2026-05-28 to 2026-05-29), qwestly[bot] opened 23 PRs
across 5 repos:
| Repo | PRs |
|---|---|
| candidate | #514–#530, #532–#534 (19) |
| candidate-catalog | #47 |
| qwestly-ui | #27 |
| public-site | #65 |
| qwestly-hire | #105 |
Local usage
The repo ships a wrapper script at scripts/gh-as-qwestly/gh-as-qwestly that
handles the full auth flow (JWT → installation token → gh command).
Prerequisites
You need three environment variables:
export GH_QWESTLY_APP_ID=3884618
export GH_QWESTLY_INSTALLATION_ID=136085062
export GH_QWESTLY_KEY_FILE=_private/qwestly.2026-05-27.private-key.pem
The private key is stored in _private/ (gitignored). Ask a team member with
access to the app settings if you need it.
Examples
# Approve a PR
./scripts/gh-as-qwestly/gh-as-qwestly pr review 42 --approve --body "LGTM 🚀"
# Leave inline review comments
./scripts/gh-as-qwestly/gh-as-qwestly pr review 42 --comment --body "Consider extracting this."
# Comment on a PR
./scripts/gh-as-qwestly/gh-as-qwestly pr comment 42 --body "Reviewed by qwestly."
# Any gh command works
./scripts/gh-as-qwestly/gh-as-qwestly api repos/Qwestly/candidate/pulls --jq '.[].title'
Convenience alias
alias gh-bot='./scripts/gh-as-qwestly/gh-as-qwestly'
How auth works
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 1. Generate JWT │ ──▶ │ 2. Exchange for │ ──▶ │ 3. Run gh with │
│ (app private │ │ installation │ │ installation │
│ key, RS256) │ │ token (1h TTL) │ │ token │
└─────────────────┘ └──────────────────┘ └─────────────────┘
- A JWT is signed with the app's private key (RS256, 10-min expiry)
- The JWT is exchanged for an installation access token via
POST /app/installations/{id}/access_tokens ghruns withGH_TOKENset to the installation token — all API calls appear asqwestly[bot]
Installation tokens expire after 1 hour. The wrapper script generates a fresh token each time, so this is transparent.
Setup reference (for app admins)
The scripts live at:
scripts/gh-as-qwestly/
├── README.md # Detailed setup and troubleshooting
├── generate-jwt.sh # Creates a JWT signed with the app's private key
└── gh-as-qwestly # Orchestrator: JWT → installation token → gh
Creating the app from scratch
- Go to GitHub Settings → Developer settings → GitHub Apps
- Create a new app with:
- Repository permissions: Pull Requests (Read & Write), Issues (Read & Write)
- Webhook: Unchecked (not needed)
- Installation: Any account
- Transfer ownership to the Qwestly org (Settings → Advanced → Transfer)
- Generate a private key and store it securely
- Install on the Qwestly org
- Note the App ID and Installation ID for env vars
Regenerating the private key
- Go to App Settings → General → Private keys
- Click Generate a private key
- Store the
.pemfile in_private/and updateGH_QWESTLY_KEY_FILE - Revoke the old key after confirming the new one works