Add input validation for SSH connection parameters (IP, username, server_name)
and server identifiers used in delete operations. This prevents command injection
attacks if ~/.spawn/history.json is corrupted or tampered with.
Changes:
- Add validateConnectionIP() - validates IPv4/IPv6 addresses and sentinels
- Add validateUsername() - validates Unix username format
- Add validateServerIdentifier() - validates server names/IDs
- Update cmdConnect() to validate all connection params before use
- Update buildDeleteScript() to validate server IDs before interpolation
- Update mergeLastConnection() to validate data from bash scripts
- Add comprehensive test coverage for all validation functions
- Bump CLI version to 0.3.3 (security patch)
Security impact:
- Prevents HIGH severity command injection via history.ip/user (issue #1381)
- Prevents MEDIUM severity command injection via server_id (issue #1380)
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
The refactor bot was too passive: it ran for 29 minutes, all 7 teammates
used plan mode, none submitted plans, and it ignored a HIGH severity
security issue plus 4 safe-to-work issues.
Root cause: plan_mode_required on ALL teammates created too much friction
for issue-driven work. Teammates had to analyze, plan, submit, and wait
for approval — all within a tight time window.
Fix: two-track spawning system:
- Issue track: teammates assigned to labeled issues (safe-to-work,
security, bug) spawn WITHOUT plan mode. The label IS the approval.
- Proactive track: teammates doing optional scanning still use plan
mode to prevent invented work.
Also:
- Diminishing Returns Rule now explicitly exempts issue-driven work
- Issue-First Policy is now forceful: labeled issues are mandates
- Team structure maps teammates to issue label types
- Cycle timeout bumped from 15 to 25 min for issue fixing
- Discovery prompt updated with same two-track pattern
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* test: add mock test coverage for all 15 Fly.io agent scripts
Fly.io had zero test coverage — every bug fixed this session (stale
tokens, FlyV1 auth, name-taken failures, SSH hangs, PATH issues) went
undetected. This adds the full mock test infrastructure:
- test/fixtures/fly/ — env vars, API assertions, fixture JSONs for
app creation, machine creation, and token validation endpoints
- test/mock-curl-script.sh — URL stripping for api.machines.dev,
body validation for machine creation, synthetic status responses,
app creation POST handler, state tracking
- test/mock.sh — mock fly/flyctl CLI binary (ssh console, auth token),
URL stripping, required field validation, base64 mock
- test/record.sh — Fly.io REST endpoints now recordable, live
create+delete cycle, error detection, auth var mapping
All 15 agent scripts (aider, claude, openclaw, etc.) are automatically
discovered and tested: 75 passed, 0 failed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use official curl installer for OpenClaw on Fly.io
bun install -g openclaw fails on Fly.io's bare Ubuntu image. Switch to
the official installer (curl -fsSL https://openclaw.ai/install.sh | bash)
which handles Node.js detection and dependency installation automatically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* 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>
Fly.io had zero test coverage — every bug fixed this session (stale
tokens, FlyV1 auth, name-taken failures, SSH hangs, PATH issues) went
undetected. This adds the full mock test infrastructure:
- test/fixtures/fly/ — env vars, API assertions, fixture JSONs for
app creation, machine creation, and token validation endpoints
- test/mock-curl-script.sh — URL stripping for api.machines.dev,
body validation for machine creation, synthetic status responses,
app creation POST handler, state tracking
- test/mock.sh — mock fly/flyctl CLI binary (ssh console, auth token),
URL stripping, required field validation, base64 mock
- test/record.sh — Fly.io REST endpoints now recordable, live
create+delete cycle, error detection, auth var mapping
All 15 agent scripts (aider, claude, openclaw, etc.) are automatically
discovered and tested: 75 passed, 0 failed.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* 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>
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>
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>
Fixes path traversal vulnerability where unvalidated filenames from
GitHub API could write files outside intended directory.
Attack vector: MITM attack or DNS hijacking could inject filenames
like "../../../../../../tmp/evil.ts" to write arbitrary files.
Fix: Validate filenames before download - block "..", "/", and "\\"
to ensure files are written only within ${dest}/cli/src/
Severity: HIGH/CRITICAL
Affects: All users running installer via curl|bash
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixes#1376 - HIGH severity path traversal in CLI installer
Fixes#1377 - MEDIUM severity unquoted variable in hetzner token extraction
Changes:
- cli/install.sh: Replace string prefix matching with canonicalized path
comparison to prevent path traversal in rm -rf cleanup. The previous
check could be bypassed with sequences like "/tmp/../../home/user".
- hetzner/lib/common.sh: Quote xargs placeholder variable to prevent
unexpected behavior if hcloud context name contains shell metacharacters.
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: auto-run gcloud auth login on expired GCP tokens
Instead of telling users to run `gcloud auth login` manually, just
run it automatically when auth check fails or instance creation hits
a reauthentication error, then retry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: prioritize clouds with CLI installed + hcloud CLI integration
When selecting a cloud provider, clouds are now sorted in 3 tiers:
1. Credentials detected (env vars set) — top priority
2. CLI installed (e.g., gcloud, hcloud, aws) — middle priority
3. Neither — default order
Also adds hcloud CLI-first support for Hetzner operations (server
create/delete/list, SSH key management, auth) with automatic fallback
to the existing REST API when hcloud is not available.
Closes#1370
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: rename aws-lightsail to aws across the project
Simplifies the cloud key from "aws-lightsail" to "aws" — AWS should
have a single entry regardless of the underlying service used.
Renames the directory, updates manifest.json matrix keys, CLI map,
test fixtures, README, and all agent scripts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: replace open-source models with Gemini Flash and Sonnet in workflows
Drop moonshotai/kimi-k2.5 and Haiku from refactor/security workflows.
Lightweight tasks (triage, issue-checker, community-coordinator) now use
google/gemini-3-flash-preview; all other teammates upgraded to Sonnet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: ensure CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 in all workflows
Add the required feature flag export to refactor.sh and security.sh
(discovery.sh already had it). Also update SKILL.md wrapper template
and agent teams reference section to document the requirement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: persist CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS into .spawnrc
All three service scripts now check for ~/.spawnrc and idempotently
append the agent teams feature flag if missing. This ensures every
Claude session on the VM inherits the flag, not just the one launched
by the service script. Also documents the pattern in SKILL.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS to qa-cycle.sh
Complete the coverage — qa-cycle.sh now also exports the agent teams
feature flag and persists it to .spawnrc, matching the other three
service scripts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracted EXIT_CODE_GUIDANCE and SIGNAL_GUIDANCE from commands.ts into a
new guidance-data.ts module. This reduces commands.ts complexity by 100+ lines,
making error handling logic more maintainable and focused.
Changes:
- New file: cli/src/guidance-data.ts (116 lines) with error/signal guidance data
- Refactored: commands.ts now 100 lines shorter, imports guidance data
- Improved: Exit code 1 handling to avoid circular dependency with credentialHints
The extracted module is a pure data file focused on error messages and guidance,
separate from the command execution logic.
Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of telling users to run `gcloud auth login` manually, just
run it automatically when auth check fails or instance creation hits
a reauthentication error, then retry.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The result event detection in refactor.sh, discovery.sh, and security.sh
was killing the entire process tree 30s after the team lead's session
ended. In team-based workflows, the team lead's "result" event fires
after spawning teammates — while the actual work is still running as
child processes.
Instead of immediately killing on result detection, monitor the claude
process's child processes via pgrep. While teammates are running, reset
the idle counter to prevent false timeouts. Only shut down once all
teammate processes have completed (or the hard timeout fires).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Redirects HOME and XDG dirs to a temp directory before tests run,
preventing any test from accidentally writing to the real user's
home directory (e.g. ~/.claude/settings.json, ~/.zshrc).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The tagline claimed "149 combinations" without context. Users might think
this is a limitation or wonder why 15×10=150. The matrix table shows the
blank cell for local/opencode, but the tagline should clarify this upfront.
Agent: ux-engineer
Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove verbose fields (dropdowns, use cases, environment, proposed UX) from
all issue templates. Humans just need to say what they want; the refactor
team handles enrichment and triage.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing commands to README commands table for consistency with CLI help:
- spawn <cloud> (show available agents)
- spawn list <filter> (filter by agent/cloud name)
- spawn list -a/-c (explicit filters)
- spawn list --clear (clear history)
- spawn last (rerun most recent)
- spawn help (show help)
- spawn version (show version)
Updated descriptions to match CLI help output exactly.
Agent: ux-engineer
Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
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>
Fixes#1197 by checking for saved credentials in ~/.config/spawn/{cloud}.json files.
This prevents false-positive credential warnings when cloud-specific credentials are saved via config files (as done by cloud setup scripts).
Advantages over PR #1288:
- Works with all credential key names (not just api_key/token)
- Handles multi-credential clouds correctly (OVH, Contabo)
- Generic approach checks for any non-empty credential value
Security review: ✅ No vulnerabilities detected
- Path traversal protected
- Safe JSON parsing
- No information disclosure
- Correct multi-cloud credential logic
UX improvements:
- Replace outdated cloud references (vultr/linode) with existing clouds (ovh/gcp) in help examples
- Add missing --debug flag to README commands table
- Ensure all documented examples reference clouds that exist in the matrix
These changes prevent user confusion when following examples in help text
and documentation.
Agent: ux-engineer
Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* 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>
Fixes 256 failing tests that spawn bun subprocesses. These tests were
failing because bun was not in the child process PATH. Ensures all
CLI test helpers pass PATH with $HOME/.bun/bin included.
Also corrects two gptme.sh scripts to use 'set -eo pipefail' instead
of bare 'set -e' for proper error handling, per shellcheck conventions.
Changes:
- 7 CLI test files: add PATH=$HOME/.bun/bin to execSync/spawnSync env
- 2 shell scripts: use set -eo pipefail for proper error handling
Results: 256 tests now passing, 0 failures in subprocess CLI tests.
Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace empty catch blocks with explicit error parameters for better debugging
and potential future error logging. Changes include:
- Add error parameter to all catch blocks (currently 7 instances)
- Enable conditional debug logging for non-fatal history write failures
- Maintain backward compatibility - no behavior changes
- Improve code maintainability and debugging capability
This addresses code health issue where errors were silently swallowed without
any reference, making debugging difficult.
Agent: code-health
Co-authored-by: test-engineer <agent@spawn.local>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
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>
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>
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>
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>
Adds destroy_server() wrapper functions to OVH and Sprite cloud libraries
to match the standardized function name used by 8 other clouds.
Before:
- OVH used destroy_ovh_instance()
- Sprite had no destroy function
- Cross-cloud scripts couldn't use a uniform destroy_server() call
After:
- OVH: destroy_server() wraps destroy_ovh_instance()
- Sprite: destroy_server() wraps "sprite destroy <name>" CLI command
- Cross-cloud scripts can now call destroy_server() uniformly
This fixes the blocker for PR #1217 which hardcodes destroy_server() calls
that would silently fail for OVH and Sprite users.
Fixes#1178
Agent: ux-engineer
Co-authored-by: spawn-bot <bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>