Fix API key changes not triggering container recreation
All checks were successful
Build App / build-linux (push) Successful in 2m45s
Build App / build-windows (push) Successful in 4m15s

The container was only recreated when the auth mode changed, not when
the API key value itself changed. This meant saving a new key required
a manual container rebuild. Now we store a hash of the API key as a
Docker label and compare it on start, so a key change automatically
recreates the container (preserving the claude config volume).

Also adds a note to the global AWS settings UI that changes require a
container rebuild.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 02:22:34 +00:00
parent 2ff270ebfe
commit c2736ace90
4 changed files with 36 additions and 137 deletions

View File

@@ -169,6 +169,7 @@ pub async fn start_project_container(
let needs_recreation = docker::container_needs_recreation(
&existing_id,
&project,
api_key.as_deref(),
settings.global_claude_instructions.as_deref(),
&settings.global_custom_env_vars,
)

View File

@@ -10,6 +10,19 @@ use std::hash::{Hash, Hasher};
use super::client::get_docker;
use crate::models::{AuthMode, BedrockAuthMethod, ContainerInfo, EnvVar, GlobalAwsSettings, Project, ProjectPath};
/// Compute a fingerprint for the API key so we can detect when it changes
/// without storing the actual key in Docker labels.
fn compute_api_key_fingerprint(api_key: Option<&str>) -> String {
match api_key {
Some(key) => {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
format!("{:x}", hasher.finish())
}
None => String::new(),
}
}
/// Compute a fingerprint string for the custom environment variables.
/// Sorted alphabetically so order changes do not cause spurious recreation.
fn compute_env_fingerprint(custom_env_vars: &[EnvVar]) -> String {
@@ -356,6 +369,7 @@ pub async fn create_container(
labels.insert("triple-c.project-id".to_string(), project.id.clone());
labels.insert("triple-c.project-name".to_string(), project.name.clone());
labels.insert("triple-c.auth-mode".to_string(), format!("{:?}", project.auth_mode));
labels.insert("triple-c.api-key-fingerprint".to_string(), compute_api_key_fingerprint(api_key));
labels.insert("triple-c.paths-fingerprint".to_string(), compute_paths_fingerprint(&project.paths));
labels.insert("triple-c.bedrock-fingerprint".to_string(), compute_bedrock_fingerprint(project));
labels.insert("triple-c.image".to_string(), image_name.to_string());
@@ -439,6 +453,7 @@ pub async fn remove_container(container_id: &str) -> Result<(), String> {
pub async fn container_needs_recreation(
container_id: &str,
project: &Project,
api_key: Option<&str>,
global_claude_instructions: Option<&str>,
global_custom_env_vars: &[EnvVar],
) -> Result<bool, String> {
@@ -477,6 +492,14 @@ pub async fn container_needs_recreation(
}
}
// ── API key fingerprint ─────────────────────────────────────────────
let expected_api_key_fp = compute_api_key_fingerprint(api_key);
let container_api_key_fp = get_label("triple-c.api-key-fingerprint").unwrap_or_default();
if container_api_key_fp != expected_api_key_fp {
log::info!("API key fingerprint mismatch, triggering recreation");
return Ok(true);
}
// ── Project paths fingerprint ──────────────────────────────────────────
let expected_paths_fp = compute_paths_fingerprint(&project.paths);
match get_label("triple-c.paths-fingerprint") {