Files
gastown/docs/design/account-management.md

5.1 KiB

Gas Town Account Management Design

Problem Statement

Claude Code users with multiple accounts (e.g., personal, work, team) need to:

  1. Switch between accounts easily when quotas are exhausted
  2. Set a global default account for all agents
  3. Override per-spawn or per-role as needed

Current State

  • Claude Code stores config in ~/.claude/ and ~/.claude.json
  • Auth tokens stored in system keychain
  • CLAUDE_CONFIG_DIR env var controls config location
  • /login command switches accounts but overwrites in-place
  • No built-in profile/multi-account support

Proposed Solution

Core Mechanism

Each registered account gets its own config directory:

~/.claude-accounts/
├── yegge/              # steve.yegge@gmail.com
│   ├── .claude/        # Full Claude Code config
│   └── .claude.json    # Root config file
├── ghosttrack/         # steve@ghosttrack.com
│   ├── .claude/
│   └── .claude.json
└── default -> ghosttrack/  # Symlink to current default

Town Configuration

File: ~/gt/mayor/accounts.json

This follows the existing pattern where town-level config lives in mayor/.

{
  "version": 1,
  "accounts": {
    "yegge": {
      "email": "steve.yegge@gmail.com",
      "description": "Personal/Gmail account",
      "config_dir": "~/.claude-accounts/yegge"
    },
    "ghosttrack": {
      "email": "steve@ghosttrack.com",
      "description": "Ghost Track business account",
      "config_dir": "~/.claude-accounts/ghosttrack"
    }
  },
  "default": "ghosttrack"
}

Environment Variable: GT_ACCOUNT

Highest priority override. Set this to use a specific account:

export GT_ACCOUNT=yegge
gt spawn gastown  # Uses yegge account

Command Interface

Account Management

# List registered accounts
gt account list
# Output:
#   yegge       steve.yegge@gmail.com     (personal)
# * ghosttrack  steve@ghosttrack.com      (default)

# Add new account (creates config dir, prompts login)
gt account add <handle> [email]
gt account add work steve@company.com

# Set default
gt account default <handle>
gt account default ghosttrack

# Remove account (keeps config dir by default)
gt account remove <handle>
gt account remove yegge --delete-config

# Check current/status
gt account status
# Output:
#   Current: ghosttrack (steve@ghosttrack.com)
#   GT_ACCOUNT env: not set

Spawn/Attach with Account Override

# Override for a specific spawn
gt spawn --account=yegge gastown

# Override for crew attach
gt crew at --account=ghosttrack max

# With env var (highest precedence)
GT_ACCOUNT=yegge gt spawn gastown

Implementation Details

Account Resolution Order

  1. GT_ACCOUNT environment variable (highest)
  2. --account flag on command
  3. default in accounts.yaml (lowest)

How Spawning Works

When gt spawn or gt crew at runs Claude Code:

func resolveAccountConfigDir() string {
    // Check env var first
    if handle := os.Getenv("GT_ACCOUNT"); handle != "" {
        return getAccountConfigDir(handle)
    }

    // Check flag
    if handle := flags.Account; handle != "" {
        return getAccountConfigDir(handle)
    }

    // Use default from config
    return getAccountConfigDir(config.Default)
}

func spawnClaudeCode(workdir string, account string) {
    configDir := resolveAccountConfigDir()

    cmd := exec.Command("claude", args...)
    cmd.Env = append(os.Environ(),
        fmt.Sprintf("CLAUDE_CONFIG_DIR=%s", configDir),
    )
    // ...
}

Account Login Flow

gt account add ghosttrack steve@ghosttrack.com
  1. Creates ~/.claude-accounts/ghosttrack/
  2. Sets CLAUDE_CONFIG_DIR and runs claude
  3. User completes /login with their account
  4. Adds entry to accounts.yaml

Security Considerations

  • No secrets in accounts.yaml - Only handles and email addresses
  • Auth tokens in keychain - Claude Code handles this per-config-dir
  • Config dir permissions - Should be user-readable only

Future Extensions

  1. Usage tracking - gt account status --usage to show quota info

  2. Auto-switching - When one account hits limits, prompt to switch

  3. Per-role defaults - Different accounts for different roles:

    role_defaults:
      witness: yegge     # Long-running patrol uses less quota
      refinery: ghosttrack
    
  4. API key accounts - For when we support direct API access:

    accounts:
      api-team:
        type: api_key
        key_ref: GT_API_KEY  # Env var containing key
    

Migration Path

Immediate (Manual)

Users can start using separate config dirs today:

# Set up account directories
mkdir -p ~/.claude-accounts/ghosttrack
export CLAUDE_CONFIG_DIR=~/.claude-accounts/ghosttrack
claude  # Login as ghosttrack

Phase 1: Basic Support

  • Add accounts.json parsing
  • Add gt account subcommands
  • Wire up GT_ACCOUNT env var in spawn

Phase 2: Full Integration

  • Add --account flags to all relevant commands
  • Add status/usage tracking
  • Add per-role defaults

Testing Plan

  1. Create test accounts config
  2. Verify spawn uses correct config dir
  3. Test override precedence
  4. Test gt account add login flow