github-api-cache
Opinionated GitHub-native development workflow with 28 skills for autonomous, issue-driven software development with Claude Code
npx skills add https://github.com/troykelly/claude-skills --skill github-api-cacheSKILL.md
GitHub API Cache
Overview
Cache GitHub project metadata ONCE at session start. All subsequent operations use cached data.
Core principle: Fetch once, extract many. Never repeat API calls for the same data.
This skill is called by session-start and provides cached data to all other skills.
The Problem
GitHub GraphQL API has a 5,000 point/hour limit. Without caching:
gh project item-list= 1 call per invocationgh project field-list= 1 call per invocationgh project list= 1 call per invocation
A typical session startup was consuming 3,500+ of 5,000 points through repeated calls.
The Solution
Fetch project metadata ONCE and cache in environment variables. All skills use cached data.
Rate Limit Check
Before any bulk operations, check available quota:
check_github_rate_limits() {
local graphql_remaining=$(gh api rate_limit --jq '.resources.graphql.remaining')
local graphql_reset=$(gh api rate_limit --jq '.resources.graphql.reset')
local rest_remaining=$(gh api rate_limit --jq '.resources.core.remaining')
echo "GraphQL: $graphql_remaining remaining"
echo "REST: $rest_remaining remaining"
if [ "$graphql_remaining" -lt 100 ]; then
local now=$(date +%s)
local wait_seconds=$((graphql_reset - now + 10))
echo "WARNING: GraphQL rate limit low. Resets in $wait_seconds seconds."
return 1
fi
return 0
}
Session Initialization (2 API Calls Total)
Run this ONCE at session start. Store results in environment.
# === GitHub API Cache Initialization ===
# Cost: 2 GraphQL API calls (field-list + item-list)
echo "Caching GitHub project metadata..."
# Verify environment
if [ -z "$GITHUB_PROJECT_NUM" ] || [ -z "$GH_PROJECT_OWNER" ]; then
echo "ERROR: GITHUB_PROJECT_NUM and GH_PROJECT_OWNER must be set"
exit 1
fi
# CALL 1: Cache all project fields
export GH_CACHE_FIELDS=$(gh project field-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
# CALL 2: Cache all project items
export GH_CACHE_ITEMS=$(gh project item-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
# Extract field IDs from cached data (NO API CALLS)
export GH_STATUS_FIELD_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .id')
export GH_TYPE_FIELD_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Type") | .id // empty')
export GH_PRIORITY_FIELD_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Priority") | .id // empty')
# Extract status option IDs from cached data (NO API CALLS)
export GH_STATUS_BACKLOG_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "Backlog") | .id')
export GH_STATUS_READY_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "Ready") | .id')
export GH_STATUS_IN_PROGRESS_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "In Progress") | .id')
export GH_STATUS_IN_REVIEW_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "In Review") | .id')
export GH_STATUS_DONE_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "Done") | .id')
export GH_STATUS_BLOCKED_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "Blocked") | .id')
# Get project ID (needed for item-edit) - extract from cached fields response
# Note: If project ID not available in fields, this requires 1 additional call
export GH_PROJECT_ID=$(gh project list --owner "$GH_PROJECT_OWNER" --format json --limit 100 | \
jq -r ".projects[] | select(.number == $GITHUB_PROJECT_NUM) | .id")
echo "Cached: $(echo "$GH_CACHE_ITEMS" | jq '.items | length') project items"
echo "GraphQL calls used: 3"
Cached Data Access Functions
These functions use ONLY cached data. NO API calls.
Get Item ID from Issue Number
get_cached_item_id() {
local issue_num=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue_num) | .id"
}
Get Item Status from Issue Number
get_cached_status() {
local issue_num=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue_num) | .status.name"
}
Get Issues by Status
get_cached_issues_by_status() {
local status=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.status.name == \"$status\") | .content.number"
}
Get All Items with Status
get_cached_items_summary() {
echo "$GH_CACHE_ITEMS" | jq -r '.items[] | {number: .content.number, title: .content.title, status: .status.name}'
}
Check if Issue is in Project
is_issue_in_project() {
local issue_num=$1
local item_id=$(get_cached_item_id "$issue_num")
[ -n "$item_id" ] && [ "$item_id" != "null" ]
}
Cache Refresh
Only refresh cach
...