You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: persist JWT to disk so process restarts skip /auth/token (v1.12.0)
Cross-process JWT cache at ~/.cache/colony-sdk/ (XDG-aware). The
existing in-memory `_token` cache survives only the lifetime of a
`ColonyClient` instance; every fresh process re-auths against
/auth/token, which the server rate-limits to 100/hr/IP. A single host
running ~10 short-lived SDK scripts plus four supervisor-rotated
dogfood agents can exhaust that budget in an hour.
This change persists the access_token + expiry to disk in
~/.cache/colony-sdk/<sha256(base_url|api_key)[:16]>.json (mode 0600,
atomic write). New processes for the same (base_url, api_key) pair
read the cached token before paying for /auth/token. A 60s safety
margin avoids handing out a token that's about to expire.
Cache invalidation:
- refresh_token() clears both in-memory + on-disk
- rotate_key() clears the OLD key's cache file BEFORE flipping api_key
- 401 responses clear the disk cache so a stale token can't resurrect
across processes
Opt-out:
- per-client: ColonyClient(..., cache_token=False)
- global: COLONY_SDK_NO_TOKEN_CACHE=1
Test sandboxing:
- COLONY_SDK_TOKEN_CACHE_DIR overrides cache dir (used by tests)
- new tests/conftest.py autouse fixture routes all tests to tmp_path
so token writes never leak into the real ~/.cache during dev
Mirrored in AsyncColonyClient — sync + async share the same cache
file for the same (base_url, api_key) pair.
11 new tests in TestTokenCachePersistence covering: first-write,
load-from-disk, expired-token miss, corrupt-cache fallthrough, both
opt-out paths, per-key and per-base-url isolation, refresh_token
side effects, 401 invalidation, and safety-margin behaviour.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+12Lines changed: 12 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,17 @@
1
1
# Changelog
2
2
3
+
## 1.12.0 — 2026-05-23
4
+
5
+
### New features
6
+
7
+
-**Cross-process JWT cache.** The SDK now persists the `/auth/token` response to disk in `~/.cache/colony-sdk/<sha256(base_url|api_key)[:16]>.json` (XDG-aware: honors `XDG_CACHE_HOME`, overridable via `COLONY_SDK_TOKEN_CACHE_DIR`). A new `ColonyClient(..., cache_token=True)` constructor arg (default-on) enables the disk cache; per-client opt-out is `cache_token=False` and global opt-out is `COLONY_SDK_NO_TOKEN_CACHE=1`. Cache writes are atomic (tmpfile + rename) and mode-0600 so a co-tenant on the same host cannot read another user's token. Reads and writes are best-effort — any IO error silently falls through to a fresh `/auth/token` call, so cache correctness is never load-bearing.
8
+
9
+
Closes the failure mode that surfaced on 2026-05-23 where a single host running ~10 short-lived SDK scripts plus four supervisor-rotated dogfood agents hit the server-side 100/hr/IP rate limit on `/auth/token`. Each fresh `ColonyClient` instance previously re-authed from zero; with this PR a new process for the same `(base_url, api_key)` pair reuses the on-disk token instead, as long as it has > 60s of life remaining (the safety margin guards against a token expiring mid-request).
10
+
11
+
The cache key includes both `base_url` and `api_key` so the same key used against prod vs staging gets independent cache files. `refresh_token()`, `rotate_key()`, and the auto-401-refresh path all invalidate the on-disk cache so a stale token cannot resurrect itself across processes. Mirrored in `AsyncColonyClient` (same cache file format and location — sync and async clients can share the cache for the same `(base_url, api_key)` pair).
12
+
13
+
11 new regression tests in `test_client.py::TestTokenCachePersistence`. A new `tests/conftest.py` autouse fixture routes the cache to a per-test `tmp_path` so existing tests don't leak token files into the developer's real cache dir.
0 commit comments