Skip to content

Commit e23c94c

Browse files
committed
feat: add token caching with expiration checking
- Cache tokens to /tmp/gh_app_token_cache.json with expiration time - Reuse cached tokens if they're still valid (with 5-minute buffer) - Only generate new tokens when cache is missing or expired - Log cache status to stderr (using cached/generating new) Tokens are now reused across multiple script runs, reducing API calls and improving performance.
1 parent 9e0877f commit e23c94c

1 file changed

Lines changed: 78 additions & 4 deletions

File tree

.claude/get_github_app_token.py

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
# ]
99
# ///
1010
"""
11-
GitHub App Token Generator
11+
GitHub App Token Generator with caching
1212
1313
Generates an installation access token from GitHub App credentials in environment.
14+
Caches tokens and reuses them until they expire (with 5-minute buffer).
1415
1516
Environment variables required:
1617
- GH_APP_ID: GitHub App ID
@@ -28,17 +29,74 @@
2829
"""
2930

3031
import base64
32+
import json
3133
import os
3234
import sys
3335
import time
36+
from datetime import datetime, timezone
37+
from pathlib import Path
3438

3539
import jwt
3640
import requests
3741

42+
# Cache file location
43+
CACHE_FILE = Path("/tmp/gh_app_token_cache.json")
44+
# Buffer time before expiration (5 minutes)
45+
EXPIRATION_BUFFER_SECONDS = 300
3846

39-
def get_installation_token():
40-
"""Generate an installation access token for the GitHub App."""
4147

48+
def get_cached_token():
49+
"""Get cached token if it exists and is still valid."""
50+
if not CACHE_FILE.exists():
51+
return None
52+
53+
try:
54+
with open(CACHE_FILE, 'r') as f:
55+
cache_data = json.load(f)
56+
57+
token = cache_data.get('token')
58+
expires_at_str = cache_data.get('expires_at')
59+
60+
if not token or not expires_at_str:
61+
return None
62+
63+
# Parse expiration time (GitHub returns ISO 8601 format)
64+
expires_at = datetime.fromisoformat(expires_at_str.replace('Z', '+00:00'))
65+
now = datetime.now(timezone.utc)
66+
67+
# Check if token is still valid (with buffer)
68+
time_until_expiry = (expires_at - now).total_seconds()
69+
70+
if time_until_expiry > EXPIRATION_BUFFER_SECONDS:
71+
print(f"Using cached token (expires in {int(time_until_expiry/60)} minutes)", file=sys.stderr)
72+
return token
73+
else:
74+
print("Cached token expired or expiring soon, generating new token", file=sys.stderr)
75+
return None
76+
77+
except (json.JSONDecodeError, ValueError, KeyError) as e:
78+
print(f"Error reading cache: {e}, generating new token", file=sys.stderr)
79+
return None
80+
81+
82+
def save_token_to_cache(token, expires_at):
83+
"""Save token and expiration to cache file."""
84+
cache_data = {
85+
'token': token,
86+
'expires_at': expires_at,
87+
'cached_at': datetime.now(timezone.utc).isoformat()
88+
}
89+
90+
try:
91+
with open(CACHE_FILE, 'w') as f:
92+
json.dump(cache_data, f)
93+
print("Token cached successfully", file=sys.stderr)
94+
except Exception as e:
95+
print(f"Warning: Failed to cache token: {e}", file=sys.stderr)
96+
97+
98+
def generate_installation_token():
99+
"""Generate a new installation access token for the GitHub App."""
42100
# Get credentials from environment
43101
app_id = os.getenv("GH_APP_ID")
44102
private_key_b64 = os.getenv("GH_APP_PRIVATE_KEY_PEM_B64")
@@ -81,12 +139,28 @@ def get_installation_token():
81139
response.raise_for_status()
82140

83141
token_info = response.json()
142+
143+
# Save to cache
144+
save_token_to_cache(token_info['token'], token_info['expires_at'])
145+
84146
return token_info['token']
85147

86148

149+
def get_token():
150+
"""Get a valid token, either from cache or by generating a new one."""
151+
# Try to get cached token first
152+
cached_token = get_cached_token()
153+
if cached_token:
154+
return cached_token
155+
156+
# Generate new token if cache miss or expired
157+
print("Generating new GitHub App token", file=sys.stderr)
158+
return generate_installation_token()
159+
160+
87161
if __name__ == "__main__":
88162
try:
89-
token = get_installation_token()
163+
token = get_token()
90164
print(token)
91165
except Exception as e:
92166
print(f"Error: {e}", file=sys.stderr)

0 commit comments

Comments
 (0)