
Github Bot
- 35 installs
- 61 repo stars
- Updated August 4, 2026
- joelhooks/joelclaw
Interact with the GitHub API as the joelclawgithub[bot] app to create PRs, push commits, manage issues, and check CI across all repos.
About
Authenticates as a GitHub App installation to perform GitHub API operations under a bot identity. A developer uses it for programmatic PRs, commits, issues, and CI checks that should appear as the bot, not a user.
- Generate a 1-hour installation token via the bundled github-token.sh
- Secrets stay in agent-secrets; log all write operations with slog
Github Bot by the numbers
- 35 all-time installs (skills.sh)
- Ranked #340 of 733 Git & Pull Requests skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joelhooks/joelclaw --skill github-botAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 35 |
|---|---|
| repo stars | ★ 61 |
| Last updated | August 4, 2026 |
| Repository | joelhooks/joelclaw ↗ |
What it does
Interact with the GitHub API as the joelclawgithub[bot] app to create PRs, push commits, manage issues, and check CI across all repos.
Files
GitHub Bot
Interact with GitHub API via the joelclawgithub[bot] GitHub App. Acts as a bot identity with access to all 280+ repos across @joelhooks personal and org accounts.
Authentication
Generate a 1-hour installation token:
TOKEN=$(SKILL_DIR/scripts/github-token.sh)Use in API calls:
curl -H "Authorization: token $TOKEN" -H "Accept: application/vnd.github+json" \
https://api.github.com/repos/OWNER/REPO/...Token expires in 1 hour. Generate a new one if a request returns 401.
Secrets
Stored in agent-secrets (do not hardcode):
| Secret | Content |
|---|---|
github_app_id | App ID (2867136) |
github_app_client_id | Client ID |
github_app_installation_id | Installation ID |
github_app_pem | RSA private key |
Bot Identity
- Commits:
joelclawgithub[bot] <2867136+joelclawgithub[bot]@users.noreply.github.com> - All API actions (comments, reviews, PRs) show as
joelclawgithub[bot] - Has read/write access to: contents, issues, PRs, actions, deployments, environments, secrets, packages, workflows, checks, statuses, hooks, projects
Logging
Log all GitHub write operations with slog:
slog write --action "ACTION" --tool "github-bot" --detail "what was done" --reason "why"Actions: create-pr, push-commit, create-issue, merge-pr, create-release, update-workflow, etc.
Pagination
GitHub API paginates at 100 items. For full listing:
PAGE=1
while true; do
RESULT=$(curl -s -H "Authorization: token $TOKEN" \
"https://api.github.com/endpoint?per_page=100&page=$PAGE")
# process $RESULT
[ "$(echo "$RESULT" | python3 -c 'import json,sys; print(len(json.load(sys.stdin)))')" -lt 100 ] && break
PAGE=$((PAGE+1))
done#!/usr/bin/env bash
# Generate a GitHub App installation access token.
# Outputs the token to stdout. Expires in 1 hour.
#
# Usage: ./github-token.sh
#
# Requires: openssl, curl, /Users/joel/.local/bin/secrets
set -euo pipefail
SECRETS=/Users/joel/.local/bin/secrets
lease_value() {
"$SECRETS" lease "$1" --ttl 2m 2>/dev/null
}
APP_ID=$(lease_value github_app_id)
INSTALL_ID=$(lease_value github_app_installation_id)
PEM=$(lease_value github_app_pem)
# Write PEM to temp file for openssl
TMPKEY=$(mktemp)
trap 'rm -f "$TMPKEY"' EXIT
echo "$PEM" > "$TMPKEY"
# Build JWT (10 min expiry)
NOW=$(date +%s)
HEADER=$(echo -n '{"alg":"RS256","typ":"JWT"}' | openssl base64 -e | tr -d '=\n' | tr '/+' '_-')
PAYLOAD=$(echo -n "{\"iat\":$((NOW-60)),\"exp\":$((NOW+300)),\"iss\":\"${APP_ID}\"}" | openssl base64 -e | tr -d '=\n' | tr '/+' '_-')
SIGNATURE=$(echo -n "${HEADER}.${PAYLOAD}" | openssl dgst -sha256 -sign "$TMPKEY" | openssl base64 -e | tr -d '=\n' | tr '/+' '_-')
JWT="${HEADER}.${PAYLOAD}.${SIGNATURE}"
# Exchange JWT for installation token
RESPONSE=$(curl -sf -X POST \
-H "Authorization: Bearer $JWT" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/app/installations/${INSTALL_ID}/access_tokens")
echo "$RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin)['token'])"