feat: upgrade default server sizes, fix Fly.io agent installs, improve E2E tests (#1428)

- Upgrade default VM sizes across clouds for better agent performance:
  - Hetzner: cpx11 → cx23 (with cx22 fallback support for deprecated types)
  - DigitalOcean: s-2vcpu-2gb → s-2vcpu-4gb
  - Daytona: 2048MB → 4096MB memory
  - Oracle: VM.Standard.E2.1.Micro → VM.Standard.A1.Flex
  - OVH: d2-2 → d2-4
- Fix Fly.io agent failures:
  - Add Node.js + build-essential to wait_for_cloud_init (fixes npm-based agents)
  - Prepend PATH in interactive_session (fixes "source not found" errors)
- Fix openclaw installs across clouds: use explicit PATH export instead of source
- Fix DigitalOcean token validation (check "uuid" not "id")
- Fix AWS cloud-init: chown .bashrc/.zshrc to ubuntu user
- Improve Hetzner fallback: add "cheapest available" as last-resort fallback
- Upgrade E2E tests: per-combo auto-fix, credential collection, robustness fixes

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ahmed Abushagur 2026-02-17 22:17:08 -08:00 committed by GitHub
parent 963144ecbd
commit 633ce8eaac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 290 additions and 136 deletions

View file

@ -120,6 +120,7 @@ su - ubuntu -c 'curl -fsSL https://claude.ai/install.sh | bash'
# Configure PATH
echo 'export PATH="${HOME}/.claude/local/bin:${HOME}/.bun/bin:${PATH}"' >> /home/ubuntu/.bashrc
echo 'export PATH="${HOME}/.claude/local/bin:${HOME}/.bun/bin:${PATH}"' >> /home/ubuntu/.zshrc
chown ubuntu:ubuntu /home/ubuntu/.bashrc /home/ubuntu/.zshrc
touch /home/ubuntu/.cloud-init-complete
chown ubuntu:ubuntu /home/ubuntu/.cloud-init-complete
CLOUD_INIT_EOF

View file

@ -16,7 +16,7 @@ echo ""
AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"
agent_install() { install_agent "openclaw" "source ~/.bashrc && bun install -g openclaw" cloud_run; }
agent_install() { install_agent "openclaw" "export PATH=\$HOME/.bun/bin:\$PATH && bun install -g openclaw" cloud_run; }
agent_env_vars() {
generate_env_config \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
@ -25,7 +25,7 @@ agent_env_vars() {
}
agent_configure() { setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" cloud_upload cloud_run; }
agent_pre_launch() {
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
cloud_run "export PATH=\$HOME/.bun/bin:\$PATH && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
wait_for_openclaw_gateway cloud_run
}
agent_launch_cmd() { echo 'source ~/.zshrc && openclaw tui'; }

View file

@ -100,7 +100,7 @@ _is_snapshot_conflict() {
_daytona_create_with_resources() {
local name="${1}"
local cpu="${DAYTONA_CPU:-2}"
local memory="${DAYTONA_MEMORY:-2048}"
local memory="${DAYTONA_MEMORY:-4096}"
local disk="${DAYTONA_DISK:-5}"
# Validate numeric env vars to prevent command injection

View file

@ -41,7 +41,7 @@ do_api() {
test_do_token() {
local response
response=$(do_api GET "/account")
if [[ "$response" == *'"id"'* ]]; then
if [[ "$response" == *'"uuid"'* ]]; then
log_info "API token validated"
return 0
else
@ -165,7 +165,7 @@ _do_check_create_error() {
# Create a DigitalOcean droplet with cloud-init
create_server() {
local name="$1"
local size="${DO_DROPLET_SIZE:-s-2vcpu-2gb}"
local size="${DO_DROPLET_SIZE:-s-2vcpu-4gb}"
local region="${DO_REGION:-nyc3}"
local image="ubuntu-24-04-x64"

View file

@ -16,7 +16,7 @@ echo ""
AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"
agent_install() { install_agent "openclaw" "source ~/.bashrc && bun install -g openclaw" cloud_run; }
agent_install() { install_agent "openclaw" "export PATH=\$HOME/.bun/bin:\$PATH && bun install -g openclaw" cloud_run; }
agent_env_vars() {
generate_env_config \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
@ -25,7 +25,7 @@ agent_env_vars() {
}
agent_configure() { setup_openclaw_config "${OPENROUTER_API_KEY}" "${MODEL_ID}" cloud_upload cloud_run; }
agent_pre_launch() {
cloud_run "source ~/.zshrc && nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &"
cloud_run "source ~/.spawnrc 2>/dev/null; export PATH=\$HOME/.bun/bin:\$PATH && nohup openclaw gateway </dev/null > /tmp/openclaw-gateway.log 2>&1 & disown"
wait_for_openclaw_gateway cloud_run
}
agent_launch_cmd() { echo 'source ~/.zshrc && openclaw tui'; }

View file

@ -338,9 +338,13 @@ wait_for_cloud_init() {
_fly_wait_for_ssh || return 1
log_step "Installing packages (this may take 1-2 minutes)..."
run_server "apt-get update -y && apt-get install -y curl unzip git zsh python3 pip" 600 || {
run_server "apt-get update -y && apt-get install -y curl unzip git zsh python3 python3-pip build-essential" 600 || {
log_warn "Package install timed out or failed, retrying..."
run_server "apt-get install -y curl unzip git zsh python3 pip" 300 || true
run_server "apt-get install -y curl unzip git zsh python3 python3-pip build-essential" 300 || true
}
log_step "Installing Node.js..."
run_server "curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && apt-get install -y nodejs" 120 || {
log_warn "Node.js install failed, npm-based agents may not work"
}
log_step "Installing bun..."
run_server "curl -fsSL https://bun.sh/install | bash" 120 || true
@ -401,9 +405,12 @@ upload_file() {
# Start an interactive SSH session on the Fly.io machine
interactive_session() {
local cmd="$1"
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
local escaped_cmd
escaped_cmd=$(printf '%q' "$cmd")
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \

View file

@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"
agent_install() {
cloud_run "curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && apt-get install -y nodejs"
install_agent "openclaw" "npm install -g --ignore-scripts openclaw@latest" cloud_run
# Node.js is installed in wait_for_cloud_init; bun install -g fails on Fly
install_agent "openclaw" "npm install -g openclaw@latest" cloud_run
}
agent_env_vars() {

View file

@ -211,7 +211,7 @@ _pick_server_type() {
local location="$1"
# Wrap the location-specific list function for interactive_pick
_list_server_types_for_current_location() { _list_server_types_for_location "$location"; }
interactive_pick "HETZNER_SERVER_TYPE" "cpx11" "server types" _list_server_types_for_current_location "cpx11"
interactive_pick "HETZNER_SERVER_TYPE" "cx23" "server types" _list_server_types_for_current_location "cx23"
unset -f _list_server_types_for_current_location
}
@ -253,7 +253,7 @@ _hetzner_get_available_ids() {
}
# Search for a compatible fallback server type when the requested one is unavailable
# Tries same CPU family first, then any family with >= specs
# Tries: 1) same CPU family with >= specs, 2) any family with >= specs, 3) cheapest available
# Prints the fallback type name on success; emits FALLBACK: info on stderr
_hetzner_find_fallback_type() {
local server_type="$1" types_response="$2" location="$3" available_ids="$4"
@ -269,12 +269,15 @@ _hetzner_find_fallback_type() {
ids_json=$(printf '%s\n' "$available_ids" | jq -Rn '[inputs | tonumber]')
local family candidates replacement
for family in "same" "any"; do
for family in "same" "any" "cheapest"; do
local filter
if [[ "$family" == "same" ]]; then
filter="select(.cpu_type == \"${wanted_cpu}\" and .cores >= ${wanted_cores} and .memory >= ${wanted_memory})"
else
elif [[ "$family" == "any" ]]; then
filter="select(.cores >= ${wanted_cores} and .memory >= ${wanted_memory})"
else
# Last resort: any available non-deprecated type (cheapest)
filter="."
fi
candidates=$(_hetzner_find_candidates "$types_response" "$location" "$ids_json" "$filter")
@ -282,6 +285,7 @@ _hetzner_find_fallback_type() {
replacement=$(printf '%s\n' "$candidates" | head -1 | cut -d'|' -f2)
local label="${wanted_cpu}"
[[ "$family" == "any" ]] && label="any"
[[ "$family" == "cheapest" ]] && label="cheapest"
printf 'FALLBACK:%s:%s:%s:%s\n' "$server_type" "$replacement" "$location" "$label" >&2
printf '%s' "$replacement"
return 0
@ -308,22 +312,30 @@ _validate_server_type_for_location() {
local types_response
types_response=$(hetzner_api GET "/server_types?per_page=50")
# Check if the requested type exists and is directly available
# Check if the requested type exists (including deprecated) for spec lookup
local wanted_id
wanted_id=$(printf '%s' "$types_response" | jq -r \
--arg name "$server_type" \
'.server_types[] | select(.name == $name and .deprecation == null) | .id')
if [[ -z "$wanted_id" ]]; then
printf 'ERROR:unknown_type\n' >&2
return 1
fi
if printf '%s\n' "$available_ids" | grep -qx "$wanted_id"; then
if [[ -n "$wanted_id" ]] && printf '%s\n' "$available_ids" | grep -qx "$wanted_id"; then
printf '%s' "$server_type"
return 0
fi
# Type is unavailable (not in available list or deprecated) — find a fallback.
# If the type exists but is deprecated, we can still read its specs for fallback matching.
if [[ -z "$wanted_id" ]]; then
# Check if it exists at all (even deprecated) so we can read specs for fallback
wanted_id=$(printf '%s' "$types_response" | jq -r \
--arg name "$server_type" \
'.server_types[] | select(.name == $name) | .id')
if [[ -z "$wanted_id" ]]; then
printf 'ERROR:unknown_type\n' >&2
return 1
fi
fi
_hetzner_find_fallback_type "$server_type" "$types_response" "$location" "$available_ids"
}

View file

@ -394,7 +394,7 @@ _launch_oci_instance() {
create_server() {
local name="${1}"
local shape="${OCI_SHAPE:-VM.Standard.E2.1.Micro}"
local shape="${OCI_SHAPE:-VM.Standard.A1.Flex}"
log_step "Creating OCI instance '${name}' (shape: ${shape})..."

View file

@ -265,7 +265,7 @@ print(json.dumps(body))
# Create an OVH Public Cloud instance
create_ovh_instance() {
local name="$1"
local flavor="${OVH_FLAVOR:-d2-2}"
local flavor="${OVH_FLAVOR:-d2-4}"
local region="${OVH_REGION:-GRA7}"
# Validate env var inputs to prevent injection into Python code

View file

@ -3120,10 +3120,10 @@ EOF
# DEFAULT_VALUE - Default value if env var unset and list is empty or choice invalid
# PROMPT_TEXT - Label shown above the menu (e.g., "locations", "server types")
# LIST_CALLBACK - Function that outputs pipe-delimited lines (first field = ID)
# DEFAULT_ID - Optional: ID to pre-select as default (e.g., "cpx11")
# DEFAULT_ID - Optional: ID to pre-select as default (e.g., "cx23")
#
# LIST_CALLBACK must output pipe-delimited lines where the first field is the selectable ID.
# Example output: "fsn1|Falkenstein|DE" or "cpx11|2 vCPU|4 GB RAM|40 GB disk"
# Example output: "fsn1|Falkenstein|DE" or "cx23|2 vCPU|4 GB RAM|40 GB disk"
#
# Display a numbered list and read user selection
# Pipe-delimited items: "id|label". Returns selected id via stdout.

View file

@ -81,6 +81,111 @@ _get_token_env_var() {
esac
}
# --- Credential helpers ---
# Try to load a token from the spawn config file into the env var.
# Returns 0 if token was loaded, 1 if not.
_load_token_from_config() {
local cloud="$1"
local token_var
token_var=$(_get_token_env_var "$cloud")
[[ -z "$token_var" ]] && return 1
# Already set — nothing to do
local current="${!token_var:-}"
[[ -n "$current" ]] && return 0
local config_file="${HOME}/.config/spawn/${cloud}.json"
[[ -f "$config_file" ]] || return 1
local saved
saved=$(python3 -c "import json, sys; data=json.load(open(sys.argv[1])); print(data.get('api_key','') or data.get('token',''))" "$config_file" 2>/dev/null)
if [[ -n "$saved" ]]; then
export "$token_var=$saved"
return 0
fi
return 1
}
# Interactive credential collection — runs BEFORE non-interactive tests.
# For each token-based cloud, ensures the env var is set by:
# 1. Checking the env var
# 2. Loading from ~/.config/spawn/{cloud}.json
# 3. Prompting the user (Enter to skip)
_collect_credentials() {
local clouds="$1"
local collected=""
local skipped=""
for cloud in $clouds; do
local token_var
token_var=$(_get_token_env_var "$cloud")
# CLI-auth clouds (aws, gcp, oracle, sprite) — no token to collect
[[ -z "$token_var" ]] && continue
# Already in env?
if [[ -n "${!token_var:-}" ]]; then
collected="${collected} ${cloud}"
continue
fi
# Try config file
if _load_token_from_config "$cloud"; then
_e2e_log "Loaded ${token_var} from ~/.config/spawn/${cloud}.json"
collected="${collected} ${cloud}"
continue
fi
# Fly: try CLI auth (fly auth token)
if [[ "$cloud" == "fly" ]] && _try_fly_cli_token; then
_e2e_log "Loaded FLY_API_TOKEN from fly CLI auth"
collected="${collected} ${cloud}"
continue
fi
# No TTY? Can't prompt — skip
if ! echo -n "" > /dev/tty 2>/dev/null; then
skipped="${skipped} ${cloud}"
continue
fi
# Interactive prompt
printf ' %s: paste %s (Enter to skip): ' "$cloud" "$token_var"
local token=""
read -r token </dev/tty
if [[ -n "$token" ]]; then
export "$token_var=$token"
collected="${collected} ${cloud}"
else
skipped="${skipped} ${cloud}"
fi
done
if [[ -n "$skipped" ]]; then
_e2e_log "Skipped (no credentials):${skipped}"
fi
}
# Try to get FLY_API_TOKEN from the flyctl CLI (fly auth token)
_try_fly_cli_token() {
local fly_cmd=""
if command -v fly &>/dev/null; then
fly_cmd="fly"
elif command -v flyctl &>/dev/null; then
fly_cmd="flyctl"
else
return 1
fi
local token
token=$("$fly_cmd" auth token 2>/dev/null) || return 1
if [[ -n "$token" ]]; then
export FLY_API_TOKEN="$token"
return 0
fi
return 1
}
# --- Credential check ---
# Check if a cloud has credentials available (non-interactive)
@ -98,7 +203,7 @@ _cloud_has_credentials() {
local) return 0 ;;
esac
# Token-based clouds: check env var, then spawn config file
# Token-based clouds: check env var, then spawn config file, then CLI
if [[ -n "$token_var" ]]; then
local token_val="${!token_var:-}"
if [[ -n "$token_val" ]]; then
@ -109,6 +214,10 @@ _cloud_has_credentials() {
if [[ -f "$config_file" ]]; then
return 0
fi
# Fly: also check CLI auth
if [[ "$cloud" == "fly" ]]; then
_try_fly_cli_token &>/dev/null && return 0
fi
fi
return 1
}
@ -314,7 +423,7 @@ _setup_noninteractive_env() {
case "$cloud" in
hetzner)
export HETZNER_LOCATION="${HETZNER_LOCATION:-fsn1}"
export HETZNER_SERVER_TYPE="${HETZNER_SERVER_TYPE:-cpx11}"
export HETZNER_SERVER_TYPE="${HETZNER_SERVER_TYPE:-cx23}"
;;
fly)
export FLY_REGION="${FLY_REGION:-iad}"
@ -491,60 +600,46 @@ _build_failure_context() {
fi
}
# Spawn one Claude agent per cloud to fix ALL failures on that cloud at once
auto_fix_cloud() {
local cloud="$1"
shift
local agents="$*"
# Spawn one Claude agent to fix a single failing combo
auto_fix_combo() {
local cloud="$1" agent="$2"
if ! command -v claude &>/dev/null; then
_e2e_log "claude CLI not found — skipping auto-fix for ${cloud}"
_e2e_log "claude CLI not found — skipping auto-fix for ${cloud}/${agent}"
return 1
fi
local agent_count=0
local prompt=""
local files_list=""
# Build combined prompt with all failures for this cloud
for agent in $agents; do
prompt="${prompt}$(_build_failure_context "$cloud" "$agent")"
prompt="${prompt}---
"
files_list="${files_list} ${cloud}/${agent}.sh"
agent_count=$((agent_count + 1))
done
local prompt
prompt=$(_build_failure_context "$cloud" "$agent")
local cloud_lib=""
if [[ -f "${REPO_ROOT}/${cloud}/lib/common.sh" ]]; then
cloud_lib=$(cat "${REPO_ROOT}/${cloud}/lib/common.sh")
fi
_e2e_log "Spawning Claude agent for ${cloud} (${agent_count} failure(s): ${agents})..."
_e2e_log "Spawning Claude agent for ${cloud}/${agent}..."
claude -p "You are fixing E2E test failures for the **${cloud}** cloud provider.
claude -p "You are fixing an E2E test failure for **${cloud}/${agent}**.
## Cloud Library (${cloud}/lib/common.sh)
\`\`\`bash
${cloud_lib}
\`\`\`
## Failures to Fix
## Failure
${prompt}
## Instructions
Fix ALL ${agent_count} failing script(s):${files_list}
Fix the failing script: ${cloud}/${agent}.sh
For each script:
1. Read the error output to understand what went wrong
2. Compare with the reference script (working on another cloud) if available
3. Fix the issue — common problems: wrong install command, missing PATH, timeout in non-TTY
4. Run \`bash -n\` on every modified file
Only modify files under ${cloud}/. Do not modify lib/common.sh or shared/." 2>&1 | tee -a "${E2E_RESULTS_DIR}/autofix_${cloud}.log" || true
Only modify files under ${cloud}/. Do not modify lib/common.sh or shared/." 2>&1 | tee -a "${E2E_RESULTS_DIR}/autofix_${cloud}_${agent}.log" || true
}
# --- Timing history ---
@ -810,68 +905,48 @@ _build_slow_context() {
fi
}
# Spawn one Claude agent per cloud to optimize ALL slow combos on that cloud
optimize_slow_cloud() {
local cloud="$1"
shift
# Remaining args: "agent:elapsed:reasons" entries
local entries="$*"
# Spawn one Claude agent to optimize a single slow combo
optimize_slow_combo() {
local cloud="$1" agent="$2" elapsed="$3" reasons="$4"
if ! command -v claude &>/dev/null; then
_e2e_log "claude CLI not found — skipping optimization for ${cloud}"
_e2e_log "claude CLI not found — skipping optimization for ${cloud}/${agent}"
return 1
fi
local agent_count=0
local prompt=""
local files_list=""
for entry in $entries; do
local agent="${entry%%:*}"
local rest="${entry#*:}"
local elapsed="${rest%%:*}"
local reasons
reasons=$(printf '%s' "${rest#*:}" | tr '|' '\n')
prompt="${prompt}$(_build_slow_context "$cloud" "$agent" "$elapsed" "$reasons")"
prompt="${prompt}---
"
files_list="${files_list} ${cloud}/${agent}.sh"
agent_count=$((agent_count + 1))
done
local prompt
prompt=$(_build_slow_context "$cloud" "$agent" "$elapsed" "$reasons")
local cloud_lib=""
if [[ -f "${REPO_ROOT}/${cloud}/lib/common.sh" ]]; then
cloud_lib=$(cat "${REPO_ROOT}/${cloud}/lib/common.sh")
fi
_e2e_log "Spawning Claude agent for ${cloud} (${agent_count} slow combo(s))..."
_e2e_log "Spawning Claude agent for ${cloud}/${agent} (${elapsed}s)..."
claude -p "You are optimizing slow E2E tests for the **${cloud}** cloud provider.
All these scripts PASS but are too slow.
claude -p "You are optimizing a slow E2E test for **${cloud}/${agent}**.
The script PASSES but is too slow.
## Cloud Library (${cloud}/lib/common.sh)
\`\`\`bash
${cloud_lib}
\`\`\`
## Slow Scripts to Optimize
## Slow Script
${prompt}
## Instructions
Optimize ALL ${agent_count} slow script(s):${files_list}
Optimize the script: ${cloud}/${agent}.sh
For each script:
1. Compare timings with the fastest peer cloud for the same agent
2. Identify what makes it slow (heavy installer, compiling native deps, unnecessary steps)
3. Make it faster — use lighter install methods, skip unnecessary setup, parallelize where possible
4. Run \`bash -n\` on every modified file
5. Don't break anything — the scripts must still pass E2E
5. Don't break anything — the script must still pass E2E
Only modify files under ${cloud}/. Do not modify lib/common.sh or shared/." 2>&1 | tee -a "${E2E_RESULTS_DIR}/optimize_${cloud}.log" || true
Only modify files under ${cloud}/. Do not modify lib/common.sh or shared/." 2>&1 | tee -a "${E2E_RESULTS_DIR}/optimize_${cloud}_${agent}.log" || true
}
# --- Main ---
@ -975,6 +1050,16 @@ main() {
# Testable clouds (excludes local, sprite which don't provision real servers the same way)
local testable_clouds="fly hetzner digitalocean ovh aws daytona gcp oracle"
# --- Credential collection (interactive) ---
# Load tokens from config files and prompt for any missing ones
# BEFORE we go non-interactive. This lets the user provide tokens
# that aren't in env vars or config files.
echo ""
_e2e_log "━━━ Credential Collection ━━━"
echo ""
_collect_credentials "$testable_clouds"
echo ""
# Discover clouds with available credentials
local available_clouds=""
if [[ -n "$filter_cloud" ]]; then
@ -1200,34 +1285,19 @@ main() {
done
echo ""
# Group slow combos by cloud and spawn one agent per cloud in parallel
local opt_clouds=""
# Spawn one Claude agent per slow combo, all in parallel
local opt_pids=""
for entry in $slow_combos; do
local combo="${entry%%:*}"
local rest="${entry#*:}"
local elapsed="${rest%%:*}"
local reasons
reasons=$(printf '%s' "${rest#*:}" | tr '|' '\n')
local cloud="${combo%%/*}"
case " $opt_clouds " in
*" $cloud "*) ;; # already listed
*) opt_clouds="${opt_clouds} ${cloud}" ;;
esac
done
local opt_pids=""
for cloud in $opt_clouds; do
# Collect all slow entries for this cloud: "agent:elapsed:reasons ..."
local cloud_entries=""
for entry in $slow_combos; do
local combo="${entry%%:*}"
local entry_cloud="${combo%%/*}"
local agent="${combo##*/}"
if [[ "$entry_cloud" == "$cloud" ]]; then
local rest="${entry#*:}"
cloud_entries="${cloud_entries} ${agent}:${rest}"
fi
done
cloud_entries=$(printf '%s' "$cloud_entries" | sed 's/^ //')
local agent="${combo##*/}"
(
optimize_slow_cloud "$cloud" $cloud_entries
optimize_slow_combo "$cloud" "$agent" "$elapsed" "$reasons"
) &
opt_pids="${opt_pids} $!"
done
@ -1249,7 +1319,7 @@ main() {
local cloud="${combo%%/*}"
local agent="${combo##*/}"
run_e2e_test "$cloud" "$agent"
run_e2e_test "$cloud" "$agent" || true
local result_file="${E2E_RESULTS_DIR}/${cloud}_${agent}.result"
local timing_file="${E2E_RESULTS_DIR}/${cloud}_${agent}.timing"
@ -1267,37 +1337,20 @@ main() {
done
fi
# Auto-fix failures — one Claude agent per cloud, all in parallel
# Auto-fix failures — one Claude agent per combo, all in parallel
if [[ "$total_fail" -gt 0 ]] && [[ "$E2E_AUTO_FIX" == "1" ]]; then
echo ""
_e2e_log "━━━ Auto-Fix Phase ━━━"
echo ""
# Group failures by cloud
local fix_clouds=""
# Spawn one agent per failing combo in parallel
local fix_pids=""
for combo in $failed_combos; do
local cloud="${combo%%/*}"
case " $fix_clouds " in
*" $cloud "*) ;;
*) fix_clouds="${fix_clouds} ${cloud}" ;;
esac
done
# Spawn one agent per cloud in parallel
local fix_pids=""
for cloud in $fix_clouds; do
local cloud_agents=""
for combo in $failed_combos; do
local c="${combo%%/*}"
local a="${combo##*/}"
if [[ "$c" == "$cloud" ]]; then
cloud_agents="${cloud_agents} ${a}"
fi
done
cloud_agents=$(printf '%s' "$cloud_agents" | sed 's/^ //')
local agent="${combo##*/}"
(
auto_fix_cloud "$cloud" $cloud_agents
auto_fix_combo "$cloud" "$agent"
) &
fix_pids="${fix_pids} $!"
done
@ -1319,7 +1372,7 @@ main() {
local cloud="${combo%%/*}"
local agent="${combo##*/}"
run_e2e_test "$cloud" "$agent"
run_e2e_test "$cloud" "$agent" || true
local result_file="${E2E_RESULTS_DIR}/${cloud}_${agent}.result"
local timing_file="${E2E_RESULTS_DIR}/${cloud}_${agent}.timing"

View file

@ -1,4 +1,4 @@
export HCLOUD_TOKEN="test-token-hetzner"
export HETZNER_SERVER_NAME="test-srv"
export HETZNER_SERVER_TYPE="cpx11"
export HETZNER_SERVER_TYPE="cx22"
export HETZNER_LOCATION="fsn1"

View file

@ -12,7 +12,7 @@
"city": "Falkenstein"
},
"server_types": {
"available": [1, 3, 5, 7, 9, 11, 22, 23, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97],
"available": [1, 3, 5, 7, 9, 11, 22, 23, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 120],
"available_for_migration": [1, 3, 5, 7, 9, 11, 22, 23, 33, 34, 35, 36],
"supported": [1, 3, 5, 7, 9, 11, 22, 23, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97]
}

View file

@ -2591,6 +2591,87 @@
],
"storage_type": "local"
},
{
"architecture": "x86",
"category": "cost_optimized",
"cores": 2,
"cpu_type": "shared",
"deprecated": false,
"deprecation": null,
"description": "CX 22",
"disk": 40,
"id": 120,
"locations": [
{
"deprecation": null,
"id": 1,
"name": "fsn1"
},
{
"deprecation": null,
"id": 2,
"name": "nbg1"
},
{
"deprecation": null,
"id": 3,
"name": "hel1"
}
],
"memory": 2,
"name": "cx22",
"prices": [
{
"included_traffic": 21990232555520,
"location": "fsn1",
"price_hourly": {
"gross": "0.0048000000000000",
"net": "0.0048000000"
},
"price_monthly": {
"gross": "2.9900000000000000",
"net": "2.9900000000"
},
"price_per_tb_traffic": {
"gross": "1.2000000000000000",
"net": "1.2000000000"
}
},
{
"included_traffic": 21990232555520,
"location": "hel1",
"price_hourly": {
"gross": "0.0048000000000000",
"net": "0.0048000000"
},
"price_monthly": {
"gross": "2.9900000000000000",
"net": "2.9900000000"
},
"price_per_tb_traffic": {
"gross": "1.2000000000000000",
"net": "1.2000000000"
}
},
{
"included_traffic": 21990232555520,
"location": "nbg1",
"price_hourly": {
"gross": "0.0048000000000000",
"net": "0.0048000000"
},
"price_monthly": {
"gross": "2.9900000000000000",
"net": "2.9900000000"
},
"price_per_tb_traffic": {
"gross": "1.2000000000000000",
"net": "1.2000000000"
}
}
],
"storage_type": "local"
},
{
"architecture": "x86",
"category": "cost_optimized",