Launcher claude.ai status sync (chat.justin active/archived mirror)¶
URL: https://mkdocs.justinsforge.com/memory/general/reference_launcher_claude_status_sync/
chat.justin's session sections mirror the claude.ai/code web UI: the primary list = sessions the web UI shows as active; the "Unlinked, archived & ended" drawer = sessions you archived on claude.ai. Archive a session in the web UI and it drops into the drawer on the next launcher refresh (~1 min), so the old manual purge workflow is gone. Built 2026-07-05, plan chat-justin-mirror-claude-web-archive-status-2026-07-05.
Data source¶
GET https://api.anthropic.com/v1/code/sessions returns every Claude Code
session with a status of "active" or "archived" (mirrors the web UI),
plus title (== the tmux session name), id (cse_<suffix>), status_bucket,
worker_status, last_event_at. Paginates via next_cursor. Auth =
Authorization: Bearer <claudeAiOauth accessToken> + anthropic-version +
anthropic-beta: claude-code-20250219. Requires OAuth scope
user:sessions:claude_code (already on the Claude Code login token).
The three scripts¶
scripts/forge_claude_oauth_token.py,get_access_token(force_refresh=False). Reads~/.claude/.credentials.json; when the token is within 5 min of expiry it refreshes viaPOST https://platform.claude.com/v1/oauth/token(grant_type=refresh_token, client_id9d1c250a-..., both env-overridable), then atomically writes the rotated token back to.credentials.json(0600 preserved). The whole read-refresh-write runs under anflockon~/.claude/.credentials.json.forge.lockso the launcher and the CLI cooperate instead of clobbering each other's refresh token.--checkprints token len + minutes-to-expiry. Why self-refresh: the launcher runs 24/7 but the token expires hourly and the CLI only refreshes while aclaudeprocess runs.scripts/forge_claude_sessions_client.py,fetch_sessions(max_pages=10). Pages the endpoint, trims rows, force-refreshes the token once on a 401.--jsondumps rows; bare run prints the active/archived count.scripts/forge_session_launcher_api.py,_claude_status_map()builds{key -> active|archived}keyed by both the bridge-id suffix (exact) and the lowercased title (fallback), TTL-cached ~45s (CLAUDE_STATUS_TTL_S,CLAUDE_STATUS_MAX_PAGES=6).list_sessions()tags each row withclaude_status; the UI (sites/justinsforge.com/chat/launcher.js,isActive()) buckets by it.
Join key¶
Local bridge id session_<suffix> and API id cse_<suffix> share <suffix>;
_bridge_suffix() strips the prefix. Primary match is by suffix (from the row's
bridge URL), fallback is tmux name == title.
Fallback contract (load-bearing)¶
If claude.ai is unreachable or the token can't refresh, _claude_status_map()
returns a stale map or None; list_sessions() then sets claude_status=None
on every row and the UI reverts to the old tmux-only rule (alive && url). A
claude.ai hiccup must never blank the launcher's session list. Pure workers /
warm-pool spares that never bridged also get claude_status=None and stay in
their existing buckets.
Behavior notes¶
- Archiving on the web moves the card only; the tmux process keeps running (Kill stays a manual button). Freeing the process is still a manual Kill.
- Service:
forge-session-launcher.service(user unit), ExecStart runs the repo working-tree file directly, so edits go live onsystemctl --user restart. chat.justinsforge.com is served by this service (thin-shell extension iframes the live app), not a CF Pages static deploy.