type: meta

slug: codex-account-switcher

topic: cdx account router + Cinnamon systray Codex CLI multi-account switcher

exec_mode: ship

scheduler: dag-parallel

land_mode: merge-to-main

base_branch: main

status_enum: BLOCKED PENDING WORKING WRITTEN REVIEWED COMMITTED

truth_rule: this file is ground truth for INTENT (goals/directions/task list); git is ground truth for DONE-NESS — reconcile statuses against git, never trust a COMMITTED line with no commit

refresh_how: native Read of THIS file — NEVER ctx_execute/ctx_search (the point is re-injecting intent into the main thread; a context-mode call keeps bytes OUT and silently kills the feature)

refresh_what: meta + every goal/methodology/intent/direction record + all WORKING and BLOCKED tasks (skip COMMITTED lines)

refresh_when:
wave/phase boundary
any task status change
after a compaction system-reminder
before calling advisor

update_rule: on status change, rewrite that task's single line in place; append new tasks/session_memory as new lines

report_rule: at session end emit brief: done / blocked / deferred / canceled — NO 'next action' (if you know it, do it)

w1.p1.t1: cursor

w1.p1.t2: north

w1.p1.t3: cursor

w1.p1.t4: north

w1.p1.t5: north

w1.p1.t6: north

w2.p1.t7: cursor

w2.p1.t8: north

w2.p1.t9: north

w3.p1.t10: cursor

w4.p1.t11: cursor

w5.p1.t12: north

w6.p1.t13: cursor

w7.p1.t14: north


type: goal

text: cdx account-router CLI: resolves which Codex account to use per-invocation (per-project override -> global default -> health-aware fallback chain) and execs real codex with CODEX_HOME pointed at that account's isolated, never-shared credential.


text: Cinnamon tray app: click-menu to set the global default Codex CLI ChatGPT account (roy/rafa/avi + user-added), never revoking tokens, never disturbing running codex background processes.


text: Live per-account rate-limit/usage display and token-health monitoring at zero model-quota cost, via codex app-server JSON-RPC account/rateLimits/read; tray-maintained health_cache.json is what cdx reads (cdx never makes a live call itself).


text: One-click repair of a broken/expired account via codex login --device-auth, dialog with Firefox-specific clickable link, copy-code, copy-full-text buttons, plus explicit collision-safety warnings.


text: Account manager: add/remove accounts, rename display aliases, scales to N accounts with zero code changes.


type: methodology

text: Contract-level plan; ship skill (fresh subagent per task + two-stage review); commit after each task.


type: intent

text: User juggles multiple real ChatGPT/Codex accounts (sometimes belonging to different real people) across concurrent background codex agents; manual auth.json cp today is error-prone and no CLI account-switch exists.


type: direction

verbatim: scalaility should be about adding accounts, and not about "multi-user".


verbatim: per-account subfolder would be safer. isn't codex open source? just find how to get the /status from the code


verbatim: need to include "token health" to make sure token is still valid, ping each account with /status every 1h to refresh rate limits + verify token health.


verbatim: make sure the link is clickable so i can just click it and it will open a new tab in firefox


verbatim: not codex, but "cdx" executable will be not just a wrapper, but also an account router. and the selection will be via the systray first, later i will introduce rules about it


verbatim:
the rules will be: which project use which account, with fallback settings. can be per project, can be global, according to my setting. "per project" overrides global
actualy we can already introduct this rules, zync.is -> avi, automixer -> rafa, multideal -> roy, so scaffold rules for now. then for global, main is rafa, fallback chain: [roy, avi]


type: checkpoint

text: Advisor-flagged gaps resolved and verified live before the cdx pivot: (1) account/rateLimits/read never rewrites auth.json (confirmed via md5 diff, both OK and 401 paths). (2) No auth-path-override flag/env var exists in codex (--help/login --help/app-server --help/doctor all checked; doctor shows a fixed 'auth file: ~/.codex/auth.json' tied to CODEX_HOME) -- confirms the isolated-CODEX_HOME-per-account model (not a lighter auth-file-only override) is required. (3) Symlinking non-auth entries (tested: config.toml) into a separate CODEX_HOME works fine for read-only commands. (4) state_5.sqlite has an account_id-keyed table (remote_control_enrollments) -- confirms identity-bearing runtime state must stay real-per-account, not shared; resolved by splitting the farm into a shared tier (skills/plugins/commands/rules/context-mode/config.toml/AGENTS.md/RTK.md/cache/models_cache.json/installation_id/version.json) and a real-per-account tier (auth.json + all sessions/history/memories/goals/state/logs sqlite + tmp).


text: Live-tested finding: 2 of 3 existing backup auth files (roy, avi) are already server-revoked (token_revoked/token_invalidated); only rafa is currently healthy. Real confirmation the repair flow is needed, not hypothetical. Fallback chain [roy, avi] therefore currently has zero healthy members until repaired.


text: Critical live finding during this session's own verification: codex login --device-auth server-side revokes an account's OTHER live sessions globally and instantly, independent of which local directory performs the login -- proven by a live test that broke the user's real active rafa session (immediately repaired). This directly motivated the cdx + per-account isolated CODEX_HOME redesign (replacing the old single-shared ~/.codex/auth.json divergence-handling model) and the three Add-account collision mitigations (explicit warning copy, show-existing-emails guard, immediate BROKEN-flagging of the pre-existing duplicate account).


text: Separately, ~/.codex/auth.json was observed to vanish mid-session while an unrelated background codex exec process (different agent job, PID 2037117) was running against the same shared ~/.codex/ -- restored from backup. Direct live proof that concurrent processes sharing one mutable auth.json is already unsafe today, independent of any multi-account complexity -- the core problem the per-account isolated CODEX_HOME model solves.


type: gated

category: policy

needs: cdx fallback-chain trigger semantics: broken-only vs broken-or-quota-exhausted

why: accounts may belong to different real people; quota-based fallback silently reroutes work (and quota consumption) onto a different account without an interactive prompt

default: proceed

status: RESOLVED

answer: proceed

resolved_by: user

source: author


note: User's answer to the fallback-trigger question: broken_or_quota_exhausted (not broken-only, despite that being the recommended/safer default) -- every fallback event must still surface a desktop notification naming both accounts, never silent, per RoutingResolver/cdx contract in Task 4/9.


type: task

desc: AccountRegistry + migration + shared-tier symlink sync

status: COMMITTED


desc: AccountHealthClient + health_cache.json writer


desc: Create repo-tracked Cinnamon autostart .desktop template at packaging/codex-account-switcher.desktop (XDG desktop-entry format: Type=Application, Exec=path to systray_codex_switcher.py entrypoint, X-GNOME-Autostart-enabled=true), then install by copying it to ~/.config/autostart/codex-account-switcher.desktop (mkdir -p parent first). Mirrors the icon-asset create-then-install pattern (Task 5). MUST commit the packaging/ file to git -- the installed copy outside the repo is a side effect, not the deliverable.


desc: Device-auth dialog UI + add-account collision warning


desc: Add-account flow with collision mitigations


desc: Background health refresh + health_cache.json writes


desc: Entrypoint wiring + seed routing_rules.json


category: irreversible

needs: crash/hang recovery during DeviceAuthFlow backup/restore

why: already spec'd: start() backs up auth.json to .bak before spawn, rollback() restores on any failure/timeout path; commit()/rollback() are the only two exits from a session

answer: proceed

resolved_by: scan-autoresolve

source: scan


category: architecture

needs: symlink vs copy for shared config/skills/plugins

why: already decided and documented in spec §3 as the tiered farm: shared/static entries symlinked, identity-bearing runtime state (incl. state_5.sqlite's account_id-keyed table) kept real per-account -- this is the resolution, not an open question

answer: proceed


needs: autostart on Cinnamon login

why: in original spec scope from session start, user-requested feature, not new

answer: proceed


needs: courtesy ~/.codex/auth.json overwritten by set_default

why: deliberate documented design (spec §3): a real, never-symlinked mirror for bare-codex fallback use, explicitly reasoned about as a courtesy default, not silent scope creep

answer: proceed


needs: repair-account device-auth revokes that same account's other live sessions

why: this is the account's OWN re-auth -- the known, live-tested, spec-documented finding (§1 finding 2) this whole redesign exists around; inherent to repairing a broken token, not an avoidable side effect

answer: proceed


needs: add-account device-auth risk + auto-flagging pre-existing duplicate BROKEN

why: same inherent re-auth risk as g6; auto-BROKEN-flagging is one of the three mitigations the user explicitly agreed to earlier this session (spec §3 'Add-account collision mitigations')

answer: proceed


needs: remove-account rmtree needs a confirm step

why: already spec'd: Task 12 behavior is a Gtk.MessageDialog YES_NO confirmation before registry.remove() is ever called

answer: proceed


needs: real project/account names in seeded routing_rules.json

why: CORRECTED, not an open question: this file is written to ~/.codex-tray/ (runtime user-data dir) at first run, never committed to git -- the git allow-list only ever stages .py/.svg/test files, so no real identifier reaches the repo

answer: proceed


needs: manual-verification step 9 deliberately triggers a real collision, revoking a real account's other sessions

why: the whole point of that step is to test the g7 mitigation live; caution noted directly in the plan's Task 15 step 9 -- run only against an account the user is prepared to re-authenticate elsewhere afterward

answer: proceed