Commit graph

1160 commits

Author SHA1 Message Date
Ahmed Abushagur
3fbdf56c4c
fix: add guardrails to prevent bots from inventing unnecessary work (#1347)
- Add team lead pre-approval gate: teammates spawn in plan mode and must
  get approval before creating any PR (hard gate, not just prompt rules)
- Add diminishing returns rule: default posture is "code is good, shut down"
- Add dedup rule: check for existing open/closed PRs before creating new ones
- Require concrete PR justification (what breaks without this change)
- Add off-limits files list (.github/workflows, .claude/skills, CLAUDE.md)
- Use git pathspec exclusions in refactor.sh to never stage protected files
- Constrain pr-maintainer to only act on approved or feedback PRs
- Reduce refactor cron from every 5 minutes to every 2 hours

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 20:24:25 -05:00
A
5f39b035c6
refactor: extract credential loading helpers to reduce complexity in test/record.sh (#1348)
Split credential loading logic into focused helper functions:
- _export_env_vars_from_fields: Extract array export logic (16 lines)
- _load_single_token_config: Extract single-token loading (14 lines)

Changes:
- try_load_config reduced from 39 to 28 lines (28% reduction)
- _load_multi_config_from_file reduced from 38 to 26 lines (32% reduction)
- Eliminated duplicate env var validation logic
- Improved readability with clear separation of concerns

All 80 tests passing. No functional changes.

Agent: complexity-hunter

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:23:49 -05:00
A
8228bf19ed
ux: fix readonly property assignment errors in terminal width tests (#1357)
The tests were failing because process.stdout.columns is a readonly property in Bun's test environment. Changed all direct assignments to use Object.defineProperty() which allows setting readonly properties during tests.

Changes:
- Added setTerminalWidth() helper in commands-compact-list.test.ts
- Updated all test cases to use Object.defineProperty() instead of direct assignment
- Fixed afterEach cleanup to properly restore original columns value
- Same fixes applied to commands-list-grid.test.ts

This ensures tests pass in Bun runtime while maintaining the same test coverage.

Agent: ux-engineer

Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:23:46 -05:00
A
925b5fa350
docs: fix misleading Sprite authentication example in README (#1328)
The README incorrectly showed SPRITE_API_KEY as an environment variable,
but Sprite uses 'sprite login' for authentication (no API key needed).

Changes:
- Remove SPRITE_API_KEY example from non-interactive mode section
- Add clarifying note that Sprite uses 'sprite login'
- Change example command from 'spawn claude sprite' to 'spawn claude hetzner'
  since Hetzner actually uses an API token (HCLOUD_TOKEN)

This prevents user confusion when trying to authenticate with Sprite.

Agent: ux-engineer

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:22:52 -05:00
A
8d533d3908
fix: add error handling for critical ID/IP extraction failures (#1323)
Prevent silent failures when cloud API responses don't contain expected
server/instance IDs or IPs. Without these checks, scripts would continue
with empty variables, leading to cryptic failures downstream (e.g., "ssh
root@" or API calls with empty IDs).

Changes:
- fly: Check FLY_MACHINE_ID after extraction, fail fast with clear error
- ovh: Check OVH_INSTANCE_ID after extraction, fail fast with clear error
- hetzner: Check HETZNER_SERVER_ID and HETZNER_SERVER_IP (+ null check for jq)
- digitalocean: Check DO_DROPLET_ID after extraction, fail fast with clear error

Impact: Improves reliability by catching API response parsing failures
immediately rather than propagating empty values to SSH/API calls.

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:22:48 -05:00
A
654352bed0
security: fix predictable temp file path in sprite upload_file_sprite (#1330)
Replace PID-based temp path with cryptographically random generation
to prevent symlink attacks on remote servers.

Severity: MEDIUM
Finding: sprite/lib/common.sh:237 used $$ (PID) for temp file naming,
which is predictable and allows symlink race attacks.

Fix: Use openssl rand or /dev/urandom for 8-byte random suffix,
matching the hardened pattern from PR #1039 for shared/common.sh.

Related: #763 (security batch tracking issue)

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:22:22 -05:00
A
4fd76a78a0
ux: add comprehensive troubleshooting section to README (#1359)
Add a new Troubleshooting section to the README with three subsections:
- Installation issues: bun version checks, manual installation, PATH setup
- Agent launch failures: credential checks, cloud alternatives, dry-run usage
- Getting help: command history, version checks, bug reporting

This helps users quickly resolve common issues without searching through
issues or documentation. Positioned right after usage examples where users
are most likely to encounter problems.

Agent: ux-engineer

Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 20:21:31 -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
0461222955
fix: correct README matrix to show actual 10 clouds instead of misleading 38 (#1314)
The README showed 38 cloud providers with 531 combinations, but manifest.json
only defines 10 clouds with 149 implemented combinations. This was extremely
misleading to users who would see checkmarks for providers that don't exist.

Updated:
- README tagline: 38 clouds → 10 clouds, 531 → 149 combinations
- Matrix table: removed 28 non-existent cloud columns
- Matrix now accurately reflects manifest.json (local, oracle, hetzner, ovh,
  fly, aws-lightsail, daytona, digitalocean, gcp, sprite)

Only missing entry is local/opencode (all other 149 combinations implemented).

Agent: ux-engineer

Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 14:06:28 -08:00
A
392fbb7049
fix: source .bashrc in launch commands so env vars are available (#1325)
Env vars (OPENROUTER_API_KEY, ANTHROPIC_BASE_URL, etc.) are written to
~/.bashrc by inject_env_vars_* functions, but launch commands only
exported PATH inline — they never sourced .bashrc. This meant Claude
started without API keys.

Previously `source ~/.bashrc` was removed because fnm's eval corrupted
PATH. fnm has been completely removed from the codebase, so it's now
safe to source .bashrc again.

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 15:00:24 -05: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
86d77bc059
fix: prevent test fixtures from leaking into manifest cache (#1220)
Tests calling loadManifest(true) with mocked fetch were writing test
manifests (only 2 agents) to the real ~/.cache/spawn/manifest.json.
This caused `spawn` to show only "Claude Code" and "Aider" instead
of all 15 agents.

Root cause: CACHE_DIR/CACHE_FILE were computed once at import time,
so tests setting XDG_CACHE_HOME in beforeEach() had no effect.

Fix:
- Make CACHE_DIR/CACHE_FILE dynamic via getter functions so test
  isolation via XDG_CACHE_HOME actually works
- Skip disk writes in test environments unless XDG_CACHE_HOME is
  explicitly set (tests that need disk cache use setupTestEnvironment
  which sets XDG_CACHE_HOME to a temp dir)
- Bump CLI version to 0.2.88

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 19:02:21 -08:00
A
4e1796230e
cli: add interactive cloud selection for spawn <agent> (#1192)
Fixes #1180

When running `spawn <agent>` (e.g., `spawn claude`), now shows an interactive
cloud picker instead of requiring the full command or showing agent info.

- Add cmdAgentInteractive() function for agent-first cloud selection
- Route `spawn <agent>` to interactive picker when in TTY mode
- Fall back to agent info display in non-interactive contexts
- Update help text to reflect new interactive behavior
- Version bump 0.2.83 → 0.2.84

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 17:36:36 -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
6c8ab77bd0
test: fix failing agent-config-setup tests (#1187)
* test: fix failing agent-config-setup tests by handling $HOME path substitution

Fixed 15 failing tests in agent-config-setup.test.ts by adding proper $HOME
path substitution in mock_run callbacks. The config setup functions use
$HOME instead of ~ in the mv commands, but the test mocks only were
replacing ~/ paths. Now all mock_run callbacks properly replace both:
- ~/ paths (for mkdir commands)
- $HOME paths (for mv commands in upload_config_file)

All 8061 CLI tests now pass. All 80 shell tests remain passing.

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

* fix: clarify monitoring loop requirements to prevent early session termination (#1194)

All four run modes (team_building, triage, review_all, scan) now have
explicit "Monitor Loop (CRITICAL)" sections with step-by-step instructions:

1. Call TaskList to check task status
2. Process completed tasks/messages
3. Call Bash("sleep 15") to wait
4. REPEAT until done or timeout

This fixes the issue where team leads would spawn teammates, then fail
to enter the monitoring loop, causing the session to end prematurely
(since "session ENDS when you produce a response with NO tool calls").

The previous vague instruction "Loop: TaskList → process → sleep 5"
was insufficient. The new format makes it crystal clear that:
- The loop must be INFINITE (keep repeating)
- EVERY iteration must include BOTH TaskList AND Bash sleep calls
- The session will end if you stop calling tools

This addresses the bug where review_all sessions ended after ~115s
instead of running the full 30min cycle.

Co-authored-by: Security Reviewer <security-reviewer@spawn.dev>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: document Sprite vs normal VM paths in SKILL.md, never invent directories (#1196)

- Add environment table: Sprite VMs use /home/sprite/, normal VMs use /root/
- Replace all hardcoded /root/spawn paths with <REPO_ROOT> placeholders
- Instruct agents to ask the user for the repo path, never guess
- Explicitly ban inventing directories like /home/claude-runner/

Co-authored-by: Security Reviewer <security-reviewer@spawn.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: fix agent-config-setup.test.ts - shell mocking for HOME variable substitution (#1195)

All 40 tests in agent-config-setup.test.ts now pass by properly handling
$HOME variable substitution in mock_run callbacks. Added createMockSetup()
helper function to DRY up repeated mock configuration across openclaw and
continue tests (16 tests total).

Changes:
- Fix mock_run() to replace $HOME before evaluating commands
- Add createMockSetup(tempDir, configDir) helper to reduce code duplication
- Update all setup_openclaw_config and setup_continue_config tests to use helper
- Ensures /tmp/spawn_config_* temp files are redirected to temp test directory

Agent: test-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* 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>

---------

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Security Reviewer <security-reviewer@spawn.dev>
Co-authored-by: pr-maintainer <pr-maintainer@spawn>
2026-02-15 16:03:43 -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
90417c2e1b
test: fix agent-config-setup.test.ts - shell mocking for HOME variable substitution (#1195)
All 40 tests in agent-config-setup.test.ts now pass by properly handling
$HOME variable substitution in mock_run callbacks. Added createMockSetup()
helper function to DRY up repeated mock configuration across openclaw and
continue tests (16 tests total).

Changes:
- Fix mock_run() to replace $HOME before evaluating commands
- Add createMockSetup(tempDir, configDir) helper to reduce code duplication
- Update all setup_openclaw_config and setup_continue_config tests to use helper
- Ensures /tmp/spawn_config_* temp files are redirected to temp test directory

Agent: test-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 05:16:11 -05:00
L
1ddf2695d0
fix: document Sprite vs normal VM paths in SKILL.md, never invent directories (#1196)
- Add environment table: Sprite VMs use /home/sprite/, normal VMs use /root/
- Replace all hardcoded /root/spawn paths with <REPO_ROOT> placeholders
- Instruct agents to ask the user for the repo path, never guess
- Explicitly ban inventing directories like /home/claude-runner/

Co-authored-by: Security Reviewer <security-reviewer@spawn.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-15 02:10:43 -08:00
L
5b4f02d9b1
fix: clarify monitoring loop requirements to prevent early session termination (#1194)
All four run modes (team_building, triage, review_all, scan) now have
explicit "Monitor Loop (CRITICAL)" sections with step-by-step instructions:

1. Call TaskList to check task status
2. Process completed tasks/messages
3. Call Bash("sleep 15") to wait
4. REPEAT until done or timeout

This fixes the issue where team leads would spawn teammates, then fail
to enter the monitoring loop, causing the session to end prematurely
(since "session ENDS when you produce a response with NO tool calls").

The previous vague instruction "Loop: TaskList → process → sleep 5"
was insufficient. The new format makes it crystal clear that:
- The loop must be INFINITE (keep repeating)
- EVERY iteration must include BOTH TaskList AND Bash sleep calls
- The session will end if you stop calling tools

This addresses the bug where review_all sessions ended after ~115s
instead of running the full 30min cycle.

Co-authored-by: Security Reviewer <security-reviewer@spawn.dev>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 01:43:17 -08:00
A
3db288c3dd
feat: trim to 9 curated launch clouds, upvote-driven discovery (#1184)
Reduce from 41 cloud providers to 10 (9 + local) curated for launch:
- local (free), oracle (free tier), hetzner (~€3.29/mo), ovh (~€3.50/mo),
  fly (free tier), aws-lightsail ($3.50/mo), daytona (pay-per-second),
  digitalocean ($4/mo), gcp ($7.11/mo), sprite (Fly.io VMs)

Changes:
- Remove 30 cloud directories, test fixtures, and provider-specific tests
- Slim manifest.json from 600 to 150 matrix entries, sorted by price
- Update CLAUDE.md with higher bar for adding clouds (prestige + pricing)
- Transform discovery service from code-implementing team to upvote-driven
  demand tracker that creates proposal issues and only implements when a
  proposal reaches 50+ upvotes
- Create GitHub issue #1183 as cloud wishlist with all dropped clouds
- Add discovery-team/cloud-proposal/agent-proposal labels
- Protect discovery-team issues from refactor team (no comments/changes)
- Fix all CLI tests (8034 pass, 0 fail) and shell tests (80 pass, 0 fail)

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 00:19:39 -08: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
1826fceee3
test: add missing coverage for cmdLast (#1176)
Added comprehensive test suite for cmdLast function (PR #1171 feature).
Covers:
- Empty history (no records)
- History with records (rerunning latest)
- Record hints and prompt display
- Helper functions (buildRecordLabel, buildRecordHint)
- Edge cases (old timestamps, metadata fields, selection logic)

Tests increased from 13,685 to 13,712 (+27 tests).

Agent: test-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 00:08:33 -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
01ed4a8158
fix: replace echo|grep with bash builtins in digitalocean/lib/common.sh (#1174)
echo "$response" | grep -q can cause "write error: Broken pipe" when
grep -q exits early and echo gets SIGPIPE. This is non-deterministic
and depends on response size and timing, which is why it only fails
intermittently in CI. Using [[ == *pattern* ]] avoids pipes entirely.

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 00:08:13 -05:00
A
89f1712761
test: fix failing test assertions to match implementations (#1173)
Updates test assertion strings in 10 test files to match current
implementation error messages. Implements changes from PR #1159
which were blocked due to merge conflicts.

Fixes #1161

Agent: test-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 23:30:27 -05:00
A
70c7f9f8c5
ux: add spawn last command to instantly rerun most recent spawn (#1171)
Adds a new `spawn last` command (with `rerun` alias) that instantly
reruns the most recent spawn from history without requiring the
interactive picker. This improves the workflow for users who frequently
want to restart their last session.

Features:
- `spawn last` or `spawn rerun` to instantly rerun last spawn
- Shows descriptive label and timestamp before rerunning
- Handles empty history gracefully with helpful message
- Preserves prompt from original spawn if it had one
- Updated help text and examples

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 23:27:59 -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
2fbe225855
refactor: extract helper functions to reduce complexity in discovery and commands (#1172)
Reduced complexity in 2 functions by extracting focused helpers:

1. preflightCredentialCheck (42 → 30 lines):
   - collectMissingCredentials(): validate env vars
   - getCredentialGuidance(): context-specific messaging
   - confirmContinueWithMissingCreds(): user confirmation logic

2. build_single_prompt (54 → 14 lines):
   - _find_first_gap(): extract matrix gap lookup
   - _print_gap_implementation_steps(): format implementation guidance
   - _print_matrix_full_guidance(): format discovery guidance

Improves testability and readability while preserving behavior.

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 23:23:26 -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
bf738bee69
ux: improve CLI help examples and remove duplicate auth text (#1163)
- Diversify help command examples to showcase more agents and clouds
  (openclaw, goose, interpreter, vultr, digitalocean, linode)
- Remove duplicate "Auth: token" text in cloud info display
- Update test to match new help examples

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 22:11:42 -05:00
Ahmed Abushagur
51de0a9dcb
refactor: remove sprite-env usage from bot scripts (#1168)
Bots no longer run on Sprite VMs. Remove all sprite-env checkpoint
calls and Sprite-specific comments/docs from automation scripts.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 22:07:56 -05:00
A
a4fe0388c1
fix: allow repo collaborators through the gate workflow (#1166)
Previously only org members were allowed. Now checks both org membership
and repo collaborator status, so invited collaborators can open issues
and PRs without being blocked.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-14 18:32:50 -08:00
A
2d8c4a4734
feat: add code-health teammate to refactor team (#1164)
New teammate that proactively scans for reliability, maintainability,
readability, testability, scalability, and best practice issues. Picks
top 3 highest-impact findings per cycle and fixes them in one PR.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-14 18:17:02 -08:00
A
2605499f1a
refactor: extract helper functions to reduce checkEntity complexity (#1153)
Split checkEntity into three focused helpers that each handle a specific
correction strategy (wrong kind, same-kind typo, opposite-kind typo).
This reduces cyclomatic complexity from 6 to 2 in the main function,
making it easier to test and understand.

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 20:48:48 -05:00