From d56c6e3845c5ca3ba883e5e7f6a4ff8bd1925a41 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Wed, 4 Mar 2026 11:41:42 -0800 Subject: [PATCH] fix: validate AWS SSO session before launching Claude for Bedrock Profile auth When using AWS Profile auth (SSO) with Bedrock, expired SSO sessions caused Claude Code to spin indefinitely. Three root causes fixed: 1. Mount host .aws at /tmp/.host-aws (read-only) and copy to /home/claude/.aws in entrypoint, mirroring the SSH key pattern. This gives AWS CLI writable sso/cache and cli/cache directories. 2. For Bedrock Profile projects, wrap the claude command in a bash script that validates credentials via `aws sts get-caller-identity` before launch. If SSO session is expired, runs `aws sso login` with the auth URL visible and clickable in the terminal. 3. Non-SSO profiles with bad creds get a warning but Claude still starts. Non-Bedrock projects are unaffected. Note: existing containers need a rebuild to pick up the new mount path. Co-Authored-By: Claude Opus 4.6 --- .../src/commands/terminal_commands.rs | 72 +++++++++++++++++-- app/src-tauri/src/docker/container.rs | 2 +- container/entrypoint.sh | 13 ++++ 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/app/src-tauri/src/commands/terminal_commands.rs b/app/src-tauri/src/commands/terminal_commands.rs index 62fe95a..13889ef 100644 --- a/app/src-tauri/src/commands/terminal_commands.rs +++ b/app/src-tauri/src/commands/terminal_commands.rs @@ -1,7 +1,74 @@ use tauri::{AppHandle, Emitter, State}; +use crate::models::{AuthMode, BedrockAuthMethod, Project}; use crate::AppState; +/// Build the command to run in the container terminal. +/// +/// For Bedrock Profile projects, wraps `claude` in a bash script that validates +/// the AWS session first. If the SSO session is expired, runs `aws sso login` +/// so the user can re-authenticate (the URL is clickable via xterm.js WebLinksAddon). +fn build_terminal_cmd(project: &Project, state: &AppState) -> Vec { + let is_bedrock_profile = project.auth_mode == AuthMode::Bedrock + && project + .bedrock_config + .as_ref() + .map(|b| b.auth_method == BedrockAuthMethod::Profile) + .unwrap_or(false); + + if !is_bedrock_profile { + return vec![ + "claude".to_string(), + "--dangerously-skip-permissions".to_string(), + ]; + } + + // Resolve AWS profile: project-level → global settings → "default" + let profile = project + .bedrock_config + .as_ref() + .and_then(|b| b.aws_profile.clone()) + .or_else(|| state.settings_store.get().global_aws.aws_profile.clone()) + .unwrap_or_else(|| "default".to_string()); + + // Build a bash wrapper that validates credentials, re-auths if needed, + // then exec's into claude. + let script = format!( + r#" +echo "Validating AWS session for profile '{profile}'..." +if aws sts get-caller-identity --profile '{profile}' >/dev/null 2>&1; then + echo "AWS session valid." +else + echo "AWS session expired or invalid." + # Check if this profile uses SSO (has sso_start_url configured) + if aws configure get sso_start_url --profile '{profile}' >/dev/null 2>&1; then + echo "Starting SSO login — click the URL below to authenticate:" + echo "" + aws sso login --profile '{profile}' + if [ $? -ne 0 ]; then + echo "" + echo "SSO login failed or was cancelled. Starting Claude anyway..." + echo "You may see authentication errors." + echo "" + fi + else + echo "Profile '{profile}' does not use SSO. Check your AWS credentials." + echo "Starting Claude anyway..." + echo "" + fi +fi +exec claude --dangerously-skip-permissions +"#, + profile = profile + ); + + vec![ + "bash".to_string(), + "-c".to_string(), + script, + ] +} + #[tauri::command] pub async fn open_terminal_session( project_id: String, @@ -19,10 +86,7 @@ pub async fn open_terminal_session( .as_ref() .ok_or_else(|| "Container not running".to_string())?; - let cmd = vec![ - "claude".to_string(), - "--dangerously-skip-permissions".to_string(), - ]; + let cmd = build_terminal_cmd(&project, &state); let output_event = format!("terminal-output-{}", session_id); let exit_event = format!("terminal-exit-{}", session_id); diff --git a/app/src-tauri/src/docker/container.rs b/app/src-tauri/src/docker/container.rs index b44e8ec..a8433dc 100644 --- a/app/src-tauri/src/docker/container.rs +++ b/app/src-tauri/src/docker/container.rs @@ -508,7 +508,7 @@ pub async fn create_container( if let Some(ref aws_path) = aws_dir { if aws_path.exists() { mounts.push(Mount { - target: Some("/home/claude/.aws".to_string()), + target: Some("/tmp/.host-aws".to_string()), source: Some(aws_path.to_string_lossy().to_string()), typ: Some(MountTypeEnum::BIND), read_only: Some(true), diff --git a/container/entrypoint.sh b/container/entrypoint.sh index f8c03cb..bdf483c 100644 --- a/container/entrypoint.sh +++ b/container/entrypoint.sh @@ -73,6 +73,19 @@ su -s /bin/bash claude -c ' sort -u -o /home/claude/.ssh/known_hosts /home/claude/.ssh/known_hosts ' +# ── AWS config setup ────────────────────────────────────────────────────────── +# Host AWS dir is mounted read-only at /tmp/.host-aws. +# Copy to /home/claude/.aws so AWS CLI can write to sso/cache and cli/cache. +if [ -d /tmp/.host-aws ]; then + rm -rf /home/claude/.aws + cp -a /tmp/.host-aws /home/claude/.aws + chown -R claude:claude /home/claude/.aws + chmod 700 /home/claude/.aws + # Ensure writable cache directories exist + mkdir -p /home/claude/.aws/sso/cache /home/claude/.aws/cli/cache + chown -R claude:claude /home/claude/.aws/sso /home/claude/.aws/cli +fi + # ── Git credential helper (for HTTPS token) ───────────────────────────────── if [ -n "$GIT_TOKEN" ]; then CRED_FILE="/home/claude/.git-credentials"