Summary
On Harper v5, the periodic token-validation path for non-expiring tokens throws on every validation attempt:
[error] Token validation error: Cannot assign to read only property 'lastValidated' of object '#<Object>'
validateAndRefreshSession mutates the session record's nested oauth object in place (src/lib/sessionValidator.ts:88):
oauthMetadata.lastValidated = now;
session.oauth = oauthMetadata;
Harper v5 freezes record values on the read path (resources/Table.ts — Object.freeze(value); also resources/tracked.ts), so session.oauth arrives frozen and the assignment throws. Harper v4 never froze records, which is why this only surfaces on v5.
Impact
- The throw happens after a successful
validateToken call but before lastValidated is persisted, and the surrounding catch swallows it — so validation "succeeds" but the timestamp never advances.
- Once a session is past
tokenValidationInterval (default 15 min), every request re-runs the provider validateToken HTTP call: error-log spam plus one provider API call per request (rate-limit exposure for GitHub-style providers).
- No functional session breakage: sessions stay valid, and revoked-token detection still works (it runs more often than intended, not less).
Observed live during a production Harper 4.7 → 5.2.6 upgrade of an internal deployment (2026-09-01), spamming on both nodes of the pair.
Affected versions
The in-place mutation is present in every 2.x release (v2.0.0 through v2.5.0 and current main) and in the 1.x line. In practice only 2.x-on-v5 matters: 1.x targets Harper 4, which doesn't freeze records. Only the non-expiring-token branch is affected — the token-refresh path already builds a fresh updatedMetadata object.
Fix
Build a new object instead of mutating the frozen one:
session.oauth = { ...oauthMetadata, lastValidated: now };
Worth a quick audit at the same time for any other in-place mutation of session.* nested objects (e.g. in clearOAuthSession) — any of them will throw the same way on v5 frozen records.
Summary
On Harper v5, the periodic token-validation path for non-expiring tokens throws on every validation attempt:
validateAndRefreshSessionmutates the session record's nestedoauthobject in place (src/lib/sessionValidator.ts:88):Harper v5 freezes record values on the read path (
resources/Table.ts—Object.freeze(value); alsoresources/tracked.ts), sosession.oautharrives frozen and the assignment throws. Harper v4 never froze records, which is why this only surfaces on v5.Impact
validateTokencall but beforelastValidatedis persisted, and the surrounding catch swallows it — so validation "succeeds" but the timestamp never advances.tokenValidationInterval(default 15 min), every request re-runs the providervalidateTokenHTTP call: error-log spam plus one provider API call per request (rate-limit exposure for GitHub-style providers).Observed live during a production Harper 4.7 → 5.2.6 upgrade of an internal deployment (2026-09-01), spamming on both nodes of the pair.
Affected versions
The in-place mutation is present in every 2.x release (v2.0.0 through v2.5.0 and current
main) and in the 1.x line. In practice only 2.x-on-v5 matters: 1.x targets Harper 4, which doesn't freeze records. Only the non-expiring-token branch is affected — the token-refresh path already builds a freshupdatedMetadataobject.Fix
Build a new object instead of mutating the frozen one:
Worth a quick audit at the same time for any other in-place mutation of
session.*nested objects (e.g. inclearOAuthSession) — any of them will throw the same way on v5 frozen records.