diff --git a/bin/disinto b/bin/disinto index 0578cbe..1c7cab2 100755 --- a/bin/disinto +++ b/bin/disinto @@ -223,7 +223,7 @@ services: - agent-data:/home/agent/data - project-repos:/home/agent/repos - ./:/home/agent/disinto:ro - - ${HOME}/.claude:/home/agent/.claude:ro + - ${HOME}/.claude:/home/agent/.claude - ${HOME}/.claude.json:/home/agent/.claude.json:ro - CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro environment: @@ -1517,6 +1517,14 @@ p.write_text(text) else echo " Mode: bare-metal" fi + echo "" + echo "── Claude authentication ──────────────────────────────" + echo " OAuth (shared across containers):" + echo " Run 'claude auth login' on the host once." + echo " Credentials in ~/.claude are mounted into containers." + echo " API key (alternative — metered billing, no rotation issues):" + echo " Set ANTHROPIC_API_KEY in .env to skip OAuth entirely." + echo "" echo " Run 'disinto status' to verify." } diff --git a/docker/agents/entrypoint.sh b/docker/agents/entrypoint.sh index 9b83d32..4fc3d17 100644 --- a/docker/agents/entrypoint.sh +++ b/docker/agents/entrypoint.sh @@ -55,6 +55,18 @@ if ! command -v claude &>/dev/null; then fi log "Claude CLI: $(claude --version 2>&1 || true)" +# ANTHROPIC_API_KEY fallback: when set, Claude uses the API key directly +# and OAuth token refresh is not needed (no rotation race). Log which +# auth method is active so operators can debug 401s. +if [ -n "${ANTHROPIC_API_KEY:-}" ]; then + log "Auth: ANTHROPIC_API_KEY is set — using API key (no OAuth rotation)" +elif [ -f /home/agent/.claude/credentials.json ]; then + log "Auth: OAuth credentials mounted from host (~/.claude)" +else + log "WARNING: No ANTHROPIC_API_KEY and no OAuth credentials found." + log "Run 'claude auth login' on the host, or set ANTHROPIC_API_KEY in .env" +fi + install_project_crons # Configure tea CLI login for forge operations (runs as agent user). diff --git a/lib/agent-session.sh b/lib/agent-session.sh index d5cae19..3306dc5 100644 --- a/lib/agent-session.sh +++ b/lib/agent-session.sh @@ -287,8 +287,20 @@ create_agent_session() { if [ -n "${CLAUDE_MODEL:-}" ]; then model_flag="--model ${CLAUDE_MODEL}" fi + + # Acquire a session-level mutex via flock to prevent concurrent Claude + # sessions from racing on OAuth token refresh (rotating the refresh token + # invalidates it for other sessions). The lock is held for the lifetime + # of the Claude process inside the tmux session. + # Use ~/.claude/session.lock so the lock is shared across containers when + # the host ~/.claude directory is bind-mounted. + local lock_dir="${HOME}/.claude" + mkdir -p "$lock_dir" + local claude_lock="${lock_dir}/session.lock" + local claude_cmd="flock -w 300 '${claude_lock}' claude --dangerously-skip-permissions ${model_flag}" + tmux new-session -d -s "$session" -c "$workdir" \ - "claude --dangerously-skip-permissions ${model_flag}" 2>/dev/null + "$claude_cmd" 2>/dev/null sleep 1 tmux has-session -t "$session" 2>/dev/null || return 1 agent_wait_for_claude_ready "$session" 120 || return 1