* fix: use uv --upgrade to ensure Python 3.13-compatible Pillow across all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: update aider/gptme/interpreter assertions from pip to uv
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: trigger test run for uv assertion fix
* fix: prevent SSH hangs, restore stderr, fix command escaping across clouds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add --pty to fly ssh console for interactive sessions
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prepend ~/.local/bin to PATH in ssh_run_server
After uv installs to ~/.local/bin, the current shell session doesn't
have it in PATH, causing "uv: command not found" on DigitalOcean and
all other SSH-based clouds (Hetzner, AWS, GCP, OVH).
Fly.io's run_server already prepends this PATH — now the shared
ssh_run_server does the same, fixing all SSH-based clouds at once.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add Node.js to cloud-init for all cloud providers
npm-based agents (codex, kilocode, etc.) fail with "npm: command not
found" because Node.js isn't installed during cloud-init. Fly.io was
the only provider installing Node.js (in wait_for_cloud_init).
Now all cloud-init scripts install Node.js v22 LTS from nodesource,
matching Fly.io's setup. Also adds ~/.local/bin to PATH in AWS and
GCP cloud-init (was already in shared/DigitalOcean/Hetzner).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use apt packages for nodejs/npm instead of nodesource
The nodesource setup script (setup_22.x) runs its own apt-get update
and repository configuration, nearly doubling cloud-init time and
causing hangs on DigitalOcean. Ubuntu 24.04 includes nodejs and npm
in its default repos — just add them to the packages list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add timeouts and better error handling to Daytona CLI commands
Daytona CLI commands (login, list, create) can hang indefinitely when
the API is slow or unreachable. This causes:
- "Failed to create sandbox: timeout" with no recovery
- Token validation timeouts misreported as "invalid token"
- Users re-entering valid tokens that also timeout
Fixes:
- Wrap all daytona CLI calls with timeout (30s for auth, 120s for create)
- Detect timeout errors separately from auth errors
- Show actionable "try again / check status" messages for timeouts
- Add nodejs/npm to Daytona wait_for_cloud_init
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: set DAYTONA_API_URL to Daytona Cloud by default
The Daytona CLI may default to connecting to a local self-hosted
server instead of Daytona Cloud. Without DAYTONA_API_URL set to
https://app.daytona.io/api, every CLI command (login, list, create)
hangs trying to reach a non-existent local server and times out.
The SDK documents this as the default, but the CLI doesn't always
pick it up — now we export it explicitly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: symlink n-installed Node.js v22 over apt v18 to prevent shadowing
n installs Node.js v22 to /usr/local/bin/node but apt's v18 at
/usr/bin/node can shadow it in non-interactive SSH sessions. After
n 22, symlink the new binaries over the apt ones so v22 is always
resolved. Also fix hcloud CLI token extraction for new TOML format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address security review, add curl timeouts to trigger workflows
- Fix ssh_run_server command injection concern: use single-quoted
path_prefix so $HOME/$PATH expand remotely, not locally
- Add --connect-timeout 15 --max-time 30 to trigger workflows to
prevent 5-min hangs when server streams responses
- Handle 409 (dedup) as success — expected when cron fires every 15min
but cycles take 35min
- Reduce workflow timeout-minutes from 5 to 2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace nested command substitution $(echo "$(whoami)") with $USER
environment variable to prevent potential command injection attacks.
The nested substitution was vulnerable because:
- whoami could be aliased or PATH-manipulated in compromised environments
- Running as root in cloud-init amplified the security impact
- Double nesting was unnecessary complexity
Using $USER is safer because:
- It's a shell variable, not command execution
- No subprocess spawning or PATH resolution
- Simpler and more reliable
Agent: test-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of telling users to run `gcloud auth login` manually, just
run it automatically when auth check fails or instance creation hits
a reauthentication error, then retry.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire up connection tracking across all 10 clouds so users can reconnect
to and delete previously spawned servers via `spawn list` and `spawn delete`.
Phase 1 - Connection tracking:
- Extend save_vm_connection() with cloud and metadata params
- Add save_vm_connection to create_server() in all cloud libs
- Extend VMConnection with cloud, deleted, deleted_at, metadata fields
Phase 2 - Delete via interactive picker:
- Add "Delete this server" option to spawn list picker
- Build delete scripts that reuse each cloud's destroy_server()
- Confirmation UX with spinner feedback
- Soft-delete marking in history (deleted records show [deleted])
Phase 3 - Standalone delete command:
- spawn delete (aliases: rm, destroy) with interactive picker
- Filter support: spawn delete -a <agent> -c <cloud>
Also improves reconnect hints for Fly (fly ssh console) and
Daytona (daytona ssh) connections.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Enhanced error messages in gcp/lib/common.sh to provide comprehensive,
platform-specific guidance for users getting started with GCP:
Changes:
- Added detailed install instructions for macOS (Homebrew), Ubuntu/Debian,
and Fedora/RHEL platforms
- Included post-installation steps (auth and project configuration)
- Added links to create GCP project and enable Compute Engine API
- Improved error message structure with clear "How to fix" sections
- Maintained color-coded output for better readability
This addresses the issue where users felt the GCP flow "doesn't guide
me toward finding the right resource". The new error messages walk
users through the complete setup process from CLI installation to
project configuration.
Agent: ux-engineer
Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Both clouds had custom `interactive_session` functions that called
`ssh` directly, bypassing the shared `ssh_interactive_session` which
shows the post-session server-still-running warning. Users ending
sessions on these clouds got no reminder to delete their server,
risking ongoing charges.
Changes:
- alibabacloud: replace custom SSH functions with shared helpers,
add SPAWN_DASHBOARD_URL pointing to ECS console
- gcp: set SSH_USER to GCP_USERNAME, replace custom SSH functions
with shared helpers, add SPAWN_DASHBOARD_URL pointing to
Compute Engine console
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GCP create_server was 64 lines (largest function across all cloud libs).
Cherry create_server was 54 lines. Both are now under 30 lines each
by extracting focused helpers:
GCP (64 -> 25 lines):
- _gcp_prepare_instance_files: startup script + SSH key temp files
- _gcp_run_create: gcloud command execution with error diagnostics
- _gcp_get_instance_ip: IP extraction from instance describe
Cherry (54 -> 27 lines):
- _cherry_build_server_body: JSON payload construction
- _cherry_submit_create: API call with error handling
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
SSH key registration in 11 cloud providers used unescaped key_name
directly in JSON request bodies. If the hostname (used to generate
key names) contained JSON-special characters like double-quotes, it
could break out of the JSON string and inject arbitrary JSON fields.
Fix: use json_escape for key_name in all providers, matching the
pattern already used by Scaleway.
Also fix GCP create_server which embedded the startup script inline
in --metadata with comma delimiters. Commas in the script could break
metadata parsing or inject additional metadata keys. Fix: use
--metadata-from-file for the startup script.
Affected providers: Hetzner, DigitalOcean, Vultr, BinaryLane,
Hostinger, Contabo, Cherry, HOSTKEY, Civo, Linode, Genesis Cloud, GCP.
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- GCP: capture gcloud stderr on failure, add common issues guidance,
use _log_diagnostic for ensure_gcloud errors
- AWS Lightsail: add common issues for create_server failure,
use _log_diagnostic for ensure_aws_cli errors,
improve instance timeout message with actionable steps
- Cherry Servers: use extract_api_error_message instead of raw response
dump, add common issues for server creation failure
- Oracle Cloud: capture OCI CLI stderr on instance launch failure,
add common issues for VCN, subnet, and instance creation errors
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
~1500 progress messages across 481 files were using log_warn (yellow)
for normal status updates like "Installing...", "Setting up...",
"Creating server...", etc. This made users think something was wrong
when everything was proceeding normally.
Changes:
- Replace log_warn with log_step for all progress/status messages
- Keep log_warn only for actual warnings (errors, remediation hints)
- Remove emoji from 3 sprite completion messages
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
- Fix triple-quote injection in SSH keys (Scaleway, UpCloud), userdata
(BinaryLane), init scripts (Civo, Kamatera), and GraphQL queries
(RunPod) by passing data via stdin/json_escape instead of inline
string interpolation
- Add input validation for all cloud provider env vars (region, type,
plan, etc.) using validate_region_name/validate_resource_name to block
shell metacharacters before they reach Python string interpolation
- Validate Modal image name as Python identifier to prevent code injection
- Validate numeric env vars (RAM, GPU count, disk size) across all providers
Affects: 19 cloud provider lib/common.sh files
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Improved the variable export pattern in gcp/lib/common.sh:
- Added SC2034 disable with clear documentation in create_server()
- Made server_ip a local variable before export
- Added SC2154 suppressions with documentation in all 10 GCP agent scripts
This eliminates shellcheck warnings while maintaining the existing
export behavior that makes GCP_SERVER_IP, GCP_INSTANCE_NAME_ACTUAL,
and GCP_ZONE available to calling scripts.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add SC2086 disable comments to interactive_session() functions in
GCP, Hetzner, and DigitalOcean providers. SSH_OPTS is intentionally
unquoted to allow word splitting for multiple SSH options.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed all 57 SC2250 shellcheck warnings by adding braces to variable
references. This improves code consistency and follows shellcheck
best practices.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added shellcheck directive comments before first SSH_OPTS usage in:
- aws-lightsail/lib/common.sh
- gcp/lib/common.sh
- lambda/lib/common.sh
- vultr/lib/common.sh
- linode/lib/common.sh
- hetzner/lib/common.sh
- digitalocean/lib/common.sh
SSH_OPTS is defined in shared/common.sh but shellcheck can't detect
cross-file variable definitions, so we suppress the warning with
an explanatory comment.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Protects against 'unbound variable' errors even if set -u is
re-enabled or inherited. Every [[ -n "$UPPER_VAR" ]] pattern now
uses [[ -n "${UPPER_VAR:-}" ]] to safely default to empty.
Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues broke the OAuth callback server on macOS:
1. echo -e doesn't work in bash 3.x — \r\n appears as literal text
in the HTTP response, browser gets malformed headers.
Fix: pre-write response with printf to a file before the subshell.
2. local variables inside ( ... ) & subshell — undefined behavior in
bash 3.x since subshells aren't function scope.
Fix: use plain variables in subshells.
3. ((elapsed++)) when elapsed=0 evaluates to falsy — set -e kills
the script on the first iteration of the timeout loop.
Fix: use elapsed=$((elapsed + 1)) instead.
Also simplified nc_listen detection to only check for BusyBox
(the -p flag check could misfire on macOS nc).
Applied to all 10 lib/common.sh files.
Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>