DEV Community

Guo Mengyue
Guo Mengyue

Posted on Originally published at Medium Fully Autonomous

I Have Five Claude Accounts and No Idea Which One This Terminal Is Spending

Disclosure: I maintain Termexo, the project this post is about. It is free and MIT licensed — there is nothing to buy. This write-up was drafted with AI assistance from my own notes and the project's source, and I have checked every technical claim in it against the code that ships in V0.7.0.

If you run more than one Claude Code or Codex terminal, you have probably hit this moment:

Your usage is nearly gone, so you open a new terminal on a different account and type claude. It starts. But which account is it actually signed in as? Nothing in the interface tells you. You are left reasoning from "did I remember to set that environment variable?" — and when you guess wrong, you quietly burn the wrong subscription.

Termexo V0.7 is mostly about that: making the account a visible, changeable property of a terminal instead of invisible state hidden in an environment variable.

I maintain Termexo. It is a Windows-only, local-first workspace for coding agents, MIT licensed. It does not replace Claude Code, Codex, or OpenCode and does not re-skin their chat interfaces — all three keep running in real PTY terminals. Termexo owns the layer above: workspaces, layout, state, sessions, tasks, and accounts.

The account lives in a directory, not in a key

The constraint that shapes everything else: for Claude Code and Codex, identity is not a swappable token. It is an entire configuration directory.

  • Claude Code reads CLAUDE_CONFIG_DIR
  • Codex reads CODEX_HOME

Credentials, identity (userID, oauthAccount, machineID), session transcripts, and caches all live under that directory. Whichever directory you point at, that is who the CLI is.

Termexo gives every managed account its own directory. "Switching accounts" therefore means changing an environment variable — and a running process cannot be told to change its own environment.

That single fact is what makes this a lifecycle problem.

Two-phase launch, so the frontend never touches secrets

Termexo already launched agents in two phases, so the switch reuses it:

1. prepare_claude_launch(terminalId, accountProfileId, profileId, …)
   → resolve the account, model, and network profiles
   → read API keys from Windows Credential Manager
   → write this terminal's hook configuration
   → stash the full environment map in LaunchEnvironmentStore, keyed by terminalId
   → return only an AgentLaunchSpec (a command string and an executable path)

2. create_terminal(terminalId, …)
   → PtyManager::start takes (not reads) that environment out of the store
   → injects it into the PTY
Enter fullscreen mode Exit fullscreen mode

The frontend never receives a key and never builds a command line. It generates terminalId with crypto.randomUUID() before step 1 and reuses it.

So switching an account becomes:

// 1. Build the new launch command first.
const launch = await prepareLaunch({
  terminalId: original.id,
  accountProfileId,          // ← the only thing that changes
  profileId: original.profileId,
  mcpProfileId: original.mcpProfileId,
  autoConfirm: original.autoConfirm,
});

// 2. Only now touch the running PTY.
await terminalGateway.close(original.id);

// 3. Restart on the new command.
state.restartTerminalWithProfile(original.id, launch.command, {
  model: original.model,
  profileId: original.profileId,
  mcpProfileId: original.mcpProfileId,
  accountProfileId,
});
Enter fullscreen mode Exit fullscreen mode

The order matters. If preparation fails — the account directory cannot be created, a credential is gone — the error is thrown before close(), and the running session is untouched. Build the replacement, then dismantle the original; never the other way round.

The restart always starts a new session, and that is honest

The dialog says "new session" in plain words. Not because resuming is hard, but because it is semantically wrong:

A transcript physically lives in the previous account's configuration directory (~/.claude/projects/**/*.jsonl). Under the new account's directory that session id does not exist, and claude --resume <id> simply fails.

So the restart clears nativeSessionId, and the UI says so up front rather than letting you discover it when your first message kills the terminal.

Reconnection has to rebuild the environment

LaunchEnvironmentStore is a take-once, in-memory store. After the app restarts, terminals reconnect — and that environment is long gone. Left alone, the CLI falls back to its default home and silently becomes a different account.

The fix is to let a terminal remember its own identity and rebuild from that record:

pub(crate) fn relaunch_environment(
    database: &WorkspaceDatabase,
    credentials: &CredentialStore,
    agent_type: &str,
    account_profile_id: Option<&str>,
    model_profile_id: Option<&str>,
    workspace_id: Option<&str>,
) -> Result<HashMap<String, String>, String>
Enter fullscreen mode Exit fullscreen mode

create_terminal takes this path whenever the stash is empty, rebuilding the account directory, proxy settings, and provider key. A terminal you switched stays switched across restarts, because the new accountProfileId was written back to its record.

One more thing: signed in, and asked to sign in again

While building this I tracked down an old annoyance: an account that had already completed claude auth login was asked to log in again the first time a terminal opened on it.

Claude Code decides whether to run its first-run wizard from a single flag in the configuration directory — hasCompletedOnboarding — and that wizard always includes a login step, regardless of whether valid credentials are sitting right there. Earlier claude auth login builds never set the flag after a successful sign-in, so every account signed in by one of them met the wizard's login screen every time.

Termexo now writes the flag when the account directory already holds credentials. Writes go through a sibling temp file and a rename, so a CLI reading concurrently never sees half a file; a damaged config is skipped and logged rather than overwritten, because Claude repairs it from its own backups and rewriting would destroy what it repairs from.

An account that has not signed in still gets the wizard — that one needs it.

What else landed in V0.7

  • The window draws its own chrome. No system title bar: the top bar spans the whole window with the window controls at its right edge, and both side panels start beneath it, the way an editor lays out.
  • Terminals render on the GPU. Long scrollback scrolls without the DOM renderer's stutter; machines without a usable GPU fall back automatically.
  • Drag-to-reorder works again for terminal tabs in the desktop build, where the webview's own drag handling had been eating the events.
  • Configuration copies between accounts — settings, instructions, plugins, and skills. Credentials, identity, and session history never travel, so both accounts stay signed in as themselves.
  • A finished sign-in is detected on its own, rather than waiting on a login CLI that keeps running after the browser flow has already returned.

Boundaries, kept on purpose

There is no Termexo account and no cloud relay. Workspaces, terminal configuration, the session index, and events live in local SQLite. API keys live in Windows Credential Manager. Native session files stay read-only — Termexo parses them and never rewrites a JSONL to make its own UI tidier.

Local-first does not mean the model requests are offline: Claude Code, Codex, and OpenCode still reach whichever provider you configured, under that provider's own terms and privacy policy. Termexo orchestrates locally; it does not proxy your requests.

Try it

Windows 10/11 x64, WebView2, Node.js 18.18+:

npx termexo@latest
Enter fullscreen mode Exit fullscreen mode

Known limits: Windows only for now, and third-party endpoints for Codex need the provider to be compatible with the Responses API — being OpenAI-compatible is not always enough.

The source and the V0.7.0 release notes are on GitHub, and there is a project site at termexo.com.

I maintain this project, so take the framing above for what it is — the account problem is the one I hit myself. The feedback I want most: when you run several coding agents at once, where does your workflow lose the most context — planning, approval, recovery, or accounts and quota?

Top comments (0)