diff --git a/CLAUDE.md b/CLAUDE.md index dedf3df..31929fd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -204,7 +204,7 @@ Two backends, selected by `CLAYDE_CLAUDE_BACKEND`: - Session resumption: saves `session_id` from JSON output, resumes via `--resume ` - Rate/usage limit detection: text-pattern matching on stdout/stderr - No per-token cost tracking (returns `cost_eur=0.0`) -- Requires: OAuth credentials mounted from host `~/.claude/.credentials.json` (see docker-compose.yml) +- Requires: a dedicated Claude config dir dir-mounted from the host (`~/clayde-claude` → `/home/clayde/.claude`); use a separate `CLAUDE_CONFIG_DIR=~/clayde-claude claude login`. Mount the directory, not the `.credentials.json` file — token refresh renames the file (new inode) and a single-file mount goes stale. See docker-compose.yml / README. --- diff --git a/README.md b/README.md index 34b34e5..2556226 100644 --- a/README.md +++ b/README.md @@ -131,9 +131,10 @@ Uses the Anthropic Python SDK with a tool-use loop. Pay-per-token. Runs the Claude Code CLI as a subprocess. Uses your Claude Pro/Max subscription — no per-token cost. -1. On the host machine, log in to the CLI: +1. On the host machine, create a **dedicated** login for the container in its + own config directory (kept separate from your personal `~/.claude`): ```bash - claude login + CLAUDE_CONFIG_DIR=~/clayde-claude claude login ``` 2. Set in `data/config.env`: ``` @@ -141,7 +142,21 @@ Runs the Claude Code CLI as a subprocess. Uses your Claude Pro/Max subscription ``` (`CLAYDE_CLAUDE_API_KEY` is not required for the CLI backend.) -The `docker-compose.yml` mounts `~/.claude/.credentials.json` from the host directly into the container. Token refreshes, logouts, and account switches on the host are immediately reflected. +The `docker-compose.yml` mounts the `~/clayde-claude` **directory** into the +container as its Claude config dir. Two things matter here: + +- **Mount the directory, not the `.credentials.json` file.** The CLI refreshes + its short-lived OAuth token by writing a new file and atomically renaming it + into place — which changes the file's inode. A single-file bind mount is + pinned to the original inode at container start, so it never sees the new + token and the container fails with "authentication expired" until you restart + the stack. A directory mount resolves the path live, so refreshes propagate + with no restart. +- **Use a dedicated login, not your personal `~/.claude`.** That directory + holds your interactive sessions, projects, and history; sharing it exposes + that state to the container. A separate login also gives the container its own + OAuth refresh-token lineage, so its token refreshes can't invalidate your + host login (refresh tokens are single-use). ### 5. Start the container diff --git a/docker-compose.yml b/docker-compose.yml index f94d12f..4aa10eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,8 +34,15 @@ services: - CLAYDE_ENABLED=true volumes: - ./data:/data - # Mount Claude CLI OAuth credentials (required when CLAYDE_CLAUDE_BACKEND=cli) - - ~/.claude/.credentials.json:/home/clayde/.claude/.credentials.json + # Dedicated Claude config dir for the container (required when + # CLAYDE_CLAUDE_BACKEND=cli). Use a SEPARATE login here — see README: + # CLAUDE_CONFIG_DIR=~/clayde-claude claude login + # Mount the DIRECTORY, not the single .credentials.json file: the CLI + # refreshes its short-lived OAuth token via atomic rename (new inode), + # which a single-file bind mount pins to the stale inode at start — + # causing "authentication expired" until the stack is restarted. A + # directory mount resolves the path live, so refreshes are picked up. + - ~/clayde-claude:/home/clayde/.claude # Pebble skill directories — mount one or more host dirs read-only # under /skills/. Subdirectory layout is free; discovery is recursive. - ~/skills/personal:/skills/personal:ro