This document explains the GitHub API caching feature that reduces the number of API requests and improves performance.
The GitHub CLI wrapper now includes intelligent caching to minimize API calls to GitHub. This helps:
- Avoid rate limiting: GitHub has rate limits (5000 requests/hour for authenticated users)
- Improve performance: Cached responses are returned instantly
- Reduce network usage: Fewer API calls means less bandwidth
- Persist across sessions: Cache is saved to disk and reloaded on restart
If you want to share cache entries across multiple machines/runners using libp2p (so only one peer needs to make a given GitHub API call), see:
The cache automatically stores responses for common operations:
list_repos()- Repository listings (5 min TTL)get_repo_info()- Repository details (5 min TTL)list_workflow_runs()- Workflow runs (60 sec TTL)get_workflow_run()- Workflow details (60 sec TTL)list_runners()- Runner status (30 sec TTL)
Different operations have different TTLs (Time-To-Live) based on how frequently the data changes.
All cache operations are thread-safe using locks, making it suitable for concurrent access.
Cache entries are automatically saved to disk (~/.cache/github_cli/) and restored on startup, so your cache survives restarts.
from ipfs_accelerate_py.github_cli import GitHubCLI
# Caching is enabled by default
gh = GitHubCLI()
# First call hits the API
repos = gh.list_repos(owner="endomorphosis", limit=10) # ~1-2 seconds
# Second call uses cache
repos = gh.list_repos(owner="endomorphosis", limit=10) # ~0.001 seconds (1000x faster!)# Disable caching entirely
gh = GitHubCLI(enable_cache=False)
# Or disable for specific calls
repos = gh.list_repos(owner="endomorphosis", use_cache=False)from ipfs_accelerate_py.github_cli import GitHubCLI, configure_cache
# Configure global cache
cache = configure_cache(
cache_dir="/custom/cache/dir",
default_ttl=600, # 10 minutes
max_cache_size=2000, # Store up to 2000 entries
enable_persistence=True
)
# Use custom cache instance
gh = GitHubCLI(cache=cache)from ipfs_accelerate_py.github_cli import get_global_cache
cache = get_global_cache()
stats = cache.get_stats()
print(f"Total requests: {stats['total_requests']}")
print(f"Cache hits: {stats['hits']}")
print(f"Cache misses: {stats['misses']}")
print(f"Hit rate: {stats['hit_rate']:.1%}")
print(f"Cache size: {stats['cache_size']}/{stats['max_cache_size']}")from ipfs_accelerate_py.github_cli import get_global_cache
cache = get_global_cache()
# Invalidate specific entry
cache.invalidate("list_repos", owner="endomorphosis", limit=10)
# Invalidate all entries matching a pattern
cache.invalidate_pattern("list_repos") # Clear all repo listings
# Clear entire cache
cache.clear()Cache keys are generated from the operation name and parameters:
list_repos:owner=endomorphosis:limit=10
get_workflow_run:repo=owner/repo:run_id=12345
This ensures different parameter combinations get separate cache entries.
- Repository listing: ~1-2 seconds per call
- Workflow runs: ~0.5-1 second per call
- API calls: Every request hits GitHub
- Repository listing: ~0.001 seconds (cached)
- Workflow runs: ~0.001 seconds (cached)
- API calls: Only when cache expires or data not cached
Example savings: If your autoscaler checks 10 repos every 60 seconds:
- Without cache: ~600 API calls/hour
- With cache: ~120 API calls/hour (80% reduction!)
gh = GitHubCLI(
gh_path="gh", # Path to gh executable
enable_cache=True, # Enable caching
cache=None, # Custom cache instance (or None for global)
cache_ttl=300 # Default TTL in seconds
)from ipfs_accelerate_py.github_cli import configure_cache
cache = configure_cache(
cache_dir=None, # Cache directory (default: ~/.cache/github_cli)
default_ttl=300, # Default TTL in seconds (5 minutes)
max_cache_size=1000, # Max entries in memory
enable_persistence=True # Save to disk
)| Operation | Default TTL | Rationale |
|---|---|---|
list_repos |
5 minutes | Repository list doesn't change often |
get_repo_info |
5 minutes | Repo metadata is relatively stable |
list_workflow_runs |
60 seconds | Workflow status changes frequently |
get_workflow_run |
60 seconds | Run details can change |
list_runners |
30 seconds | Runner status changes very frequently |
Run the test script to see cache performance:
python test_github_cache.pyExample output:
GitHub API Cache Performance Test
============================================================
1. First call to list_repos (cache miss):
Time: 1.234s
Repos found: 10
2. Second call to list_repos (cache hit):
Time: 0.001s
Repos found: 10
Speed improvement: 1234.0x faster
3. Cache Statistics:
Total requests: 2
Cache hits: 1
Cache misses: 1
Hit rate: 50.0%
Cache size: 1/500
Cache Benefits:
============================================================
✓ Cached requests are 1234.0x faster
✓ Reduced API calls by 50.0%
✓ Cache persists across sessions
✓ Automatic expiration prevents stale data
- Use defaults: The default TTLs are well-tuned for most use cases
- Monitor cache stats: Check hit rates to ensure cache is effective
- Invalidate when needed: If you make changes (e.g., create a runner), invalidate related cache
- Adjust TTL for your use case: Longer TTL = fewer API calls but potentially stale data
- Consider disk space: Persistent cache uses disk space; clear periodically if needed
The autoscaler automatically benefits from caching:
from ipfs_accelerate_py.github_autoscaler import GitHubRunnerAutoscaler
# Autoscaler uses cached GitHub CLI by default
autoscaler = GitHubRunnerAutoscaler(
owner="endomorphosis",
interval=60, # Check every 60 seconds
# GitHub API calls are automatically cached!
)
autoscaler.start()This means:
- Repository lists are cached for 5 minutes
- Workflow runs are cached for 60 seconds
- Runner status is cached for 30 seconds
- 80%+ reduction in API calls for typical autoscaler usage
Check that caching is enabled:
gh = GitHubCLI(enable_cache=True) # Ensure this is TrueIf you're getting stale data, reduce the TTL:
cache = configure_cache(default_ttl=60) # 1 minute instead of 5Or invalidate specific entries:
cache.invalidate_pattern("list_workflow_runs")Reduce the cache size:
cache = configure_cache(max_cache_size=100) # Store fewer entriesOr disable persistence:
cache = configure_cache(enable_persistence=False)If you get permission errors for the cache directory, specify a custom location:
cache = configure_cache(cache_dir="/tmp/github_cache")The cache uses:
- LRU eviction: Least-recently-used entries are evicted when cache is full
- MD5 hashing: Not used (keys are human-readable strings)
- JSON serialization: For disk persistence
- Threading locks: For thread safety
- Timestamp-based expiration: Each entry has a creation timestamp and TTL
Potential improvements:
- Cache warming (pre-fetch commonly accessed data)
- Smart invalidation (invalidate related entries automatically)
- Compression for disk storage
- Memory-mapped cache for very large datasets
- Redis/Memcached backend option for distributed caching