listSessionCredentialPaths scanned <sessions>/<folder>/.claude/.credentials.json,
but real session creds live at
<sessions>/<folder>/services/<serviceId>/.claude/.credentials.json (and under
tasks/<taskId>/...). The mismatch meant writeCredentials' fan-out and
loadFreshestCredentials' stale-copy scan silently missed every real session
file — so old refresh-token copies (family-revocation landmines) were never
overwritten and the session→canonical write-back could not converge dormant
sessions.
Rewrite it via the pure, tested collectSessionCredentialPaths that walks the
actual services/<id>/.claude and services/<id>/tasks/<id>/.claude layout.
Verified on live data: now matches all 24 real session credential files (was 0).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause of recurring "Refresh token expired / invalid_grant" logouts: the
main refresh loop and each agent session's Claude CLI share one OAuth token
family but refresh independently. Anthropic rotates refresh tokens and revokes
the whole family if an already-rotated token is reused, so when a session
refreshed mid-turn the canonical copy went stale and its next refresh was
rejected — forcing a manual re-login.
Add syncClaudeSessionAuthBack (mirrors the existing Codex syncCodexSessionAuthBack):
after each Claude turn, if the session's CLAUDE_CONFIG_DIR credentials are
strictly newer than canonical (and same subscription), adopt them into the
canonical file. writeCredentials then fans the current token out to every
session dir, so no stale copy lingers to trigger family revocation. Decision
logic extracted to the pure, tested shouldAdoptSessionOAuth.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>