Extract three helpers from the 82-line, 14-conditional function:
- _parse_cloud_auths: extract cloud auth specs from manifest.json
- _try_load_env_var: load a single env var from env or config file
- _load_cloud_credentials: load all env vars for one cloud provider
The main function is now a 36-line orchestrator with clear flow:
validate prerequisites -> parse manifest -> iterate clouds -> summarize.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Cover printAgentQuickStart (commands.ts) which has zero test coverage:
- Single-auth and multi-auth cloud credential display
- URL hint placement (only on first auth var)
- All/partial/no credentials detection ("ready to go" vs export lines)
- No-auth cloud (auth="none") handling
- Agent info header, install line, available clouds listing
- Credential prioritization in cloud ordering
- Grouped cloud type display and credential indicators
- Pure logic replica tests for quick-start computation
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements Webdock cloud provider with full API integration:
- webdock/lib/common.sh with REST API primitives
- claude.sh, cline.sh, aider.sh agent scripts
- Test coverage in test/record.sh and test/mock.sh
- manifest.json updated with cloud entry and matrix
- README.md with usage documentation
Webdock offers affordable European VPS (€2.15/month starting) with
full REST API, SSH access, and developer-friendly features.
Agent: cloud-scout-1
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- Show list-specific flags (-a, -c, --clear) in unknown flag error
- Add specific error for empty prompt files instead of generic validation
- Document SPAWN_UNICODE=1 env var in help text and troubleshooting
- Show filter/clear hints in interactive list picker
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Implements CloudSigma matrix entries for openclaw, nanoclaw, interpreter, continue, gemini, and codex. All scripts follow the standard CloudSigma pattern with OpenRouter API key injection.
Agent: gap-filler
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- fly/lib/common.sh: Replace 23-line get_server_name() that duplicated
env-var-check, prompt, and validation logic with a one-line call to the
shared get_validated_server_name helper, matching all other cloud providers.
- oracle/lib/common.sh: Break _setup_vcn_networking (48 lines, 3 distinct
responsibilities) into focused helpers:
- _create_internet_gateway: creates the IGW resource
- _add_default_route: configures the route table
- _add_ssh_security_rules: opens SSH port in the security list
The orchestrator _setup_vcn_networking now delegates to these three helpers.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Ensures closing a PR also deletes its remote branch, consistent with
how gh pr merge already uses --delete-branch. Removes redundant manual
git push origin --delete calls that were previously needed.
Fixes#942
Agent: pr-maintainer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SLACK_WEBHOOK was embedded directly in heredocs at three locations,
allowing potential command injection if the env var contained shell
metacharacters. Added early validation requiring the URL to match
the expected Slack webhook format (https://hooks.slack.com/...).
Also stopped leaking the full webhook URL into prompt text.
Fixes#992
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Validates that test/mock.sh and test/record.sh stay in sync with
manifest.json. When a new cloud provider is added, CLAUDE.md mandates
updating both files with endpoint mappings, auth env vars, and API
dispatchers. These tests catch configuration drift automatically:
- ALL_RECORDABLE_CLOUDS completeness and no duplicates
- get_endpoints(), get_auth_env_var(), call_api() coverage parity
- _strip_api_base() URL patterns match fixture directories
- Fixture directories have required _env.sh and _metadata.json
- Auth env vars in record.sh match manifest auth fields
- Shell script conventions (shebang, set -eo pipefail, no echo -e)
- Test infrastructure conventions (NO_COLOR, cleanup traps, counters)
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace fragile blocklist validation and printf '%q' escaping in upload_file()
with strict allowlist regex [a-zA-Z0-9/_.~-]+ across all non-SSH cloud providers.
For codesandbox, additionally migrate from shell command interpolation to SDK
filesystem API via environment variables, eliminating the injection surface entirely.
Affected clouds: codesandbox, daytona, e2b, fly, koyeb, modal, northflank,
railway, render, sprite
Fixes#989
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract platform-specific install logic from monolithic installer functions
into small, focused helpers. Both functions had nested OS/package-manager
cascades (depth 3-4) that made the control flow hard to follow.
ensure_jq (shared/common.sh):
- Extract _install_jq_brew, _install_jq_apt, _install_jq_dnf, _install_jq_apk
- Extract _report_jq_not_found for the fallthrough error message
- Main function becomes a clean dispatcher + verification
ensure_gh_cli + _install_gh_binary (shared/github-auth.sh):
- Extract _install_gh_brew, _install_gh_apt, _install_gh_dnf
- Extract _detect_gh_platform, _fetch_gh_latest_version, _download_and_install_gh
- _install_gh_binary drops from 71 to 12 lines as a clean orchestrator
- ensure_gh_cli drops from 57 to 29 lines
No behavior changes. All tests pass, bash -n passes.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, Phase 3 fix agents only got the last 10 lines grepped from
the log file per failing script. This was often insufficient to diagnose
the root cause. Now runs `bash test/mock.sh {cloud}` per failing cloud
and feeds the complete output to the fix agent.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Both mock.sh and record.sh now run each cloud's tests/recordings
concurrently as background jobs instead of sequentially.
Results are aggregated after all clouds finish.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a cloud's fixture recording fails 3+ consecutive QA cycles, the
system now auto-creates a GitHub issue flagging the persistent failure.
This catches stale API keys, changed endpoints, and other silent
regressions that would otherwise go unnoticed.
- Persistent tracker at .docs/qa-record-failures.json (git-ignored)
- Counter increments on failure, resets on success
- Deduplicates: skips issue creation if one already exists for that cloud
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The bot was under-updating test/mock.sh when adding new clouds because
the prompt only mentioned URL stripping. Now lists all 4 required
mock.sh functions and all 5 required record.sh functions explicitly.
Also adds a "Mock Test Infrastructure" reference table to CLAUDE.md so
both human contributors and bots know exactly what to update.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Validate SPAWN_HOME is an absolute path, reject relative paths to prevent
unintended file writes (addresses #980)
- Resolve SPAWN_HOME to canonical form to collapse .. segments
- Strip __proto__, constructor, and prototype keys from parsed manifest JSON
to prevent prototype pollution (addresses #979)
- Apply sanitization to all manifest ingestion paths (GitHub fetch, disk cache,
local dev manifest)
- Add 12 tests covering path validation and JSON sanitization
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add interactive confirmation prompt before clearing spawn history
(spawn list --clear) to prevent accidental data loss
- Show total prompt length in dry-run preview when prompt exceeds 100
characters, so users can verify the correct prompt was loaded
- Add "Rerun previous" suggestion to non-interactive terminal fallback
- Show "(shown first)" hint when clouds with credentials are detected
in interactive picker, so users understand the sort order
- Add repository URL to spawn version output for discoverability
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
* fix: strip ANSI colors before grepping test summary
The mock test output uses ANSI escape codes for colored ✓/✗/━━━
characters, so the grep in the Post summary step couldn't match
them. Strip colors with sed first.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use NO_COLOR standard instead of sed to strip ANSI codes
mock.sh now respects the NO_COLOR env var (https://no-color.org/).
CI sets NO_COLOR=1 so grep matches ✓/✗/━━━ cleanly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The QA cycle was auto-merging stale QA PRs that were mergeable.
Now it only closes stale ones — merging is left for human review.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The `spawn <agent>` quick start section was only showing the first auth
env var when the best available cloud requires multiple credentials
(e.g., UpCloud with UPCLOUD_USERNAME + UPCLOUD_PASSWORD). This left
users confused about what other credentials they needed.
Now iterates over all auth vars, consistent with `spawn <cloud>` info.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Add startup validation for the TARGET_SCRIPT env var to prevent
arbitrary script execution. The validation:
- Requires .sh extension
- Checks the file exists
- Resolves symlinks and relative paths via realpathSync
- Verifies the real path is inside the allowed skill directory
Fixes#970
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Runs `bash test/mock.sh` on every pull request targeting main.
Includes concurrency grouping to cancel stale runs and a 10-minute
timeout. Results are posted to the GitHub Actions step summary.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Improve error messages in shared utilities and cloud providers that
previously showed bare "Failed to..." messages without telling users
how to fix the problem.
Shared (shared/common.sh):
- generate_ssh_key_if_missing: handle ssh-keygen/mkdir failures with
disk space and permission guidance
- get_ssh_fingerprint: detect missing/corrupt public key files with
regeneration instructions
- generic_ssh_wait: structured "How to fix" with manual SSH test command
and firewall check
- _report_api_failure: add DNS/firewall/proxy guidance for network errors
- ensure_jq: platform-specific install commands for unknown package
managers, hash rehash hint after install
- get_openrouter_api_key_manual: structured guidance after 3 failed
attempts
Cloud providers:
- Contabo: actionable guidance for OAuth token failures
- Exoscale: guidance for credential validation and CLI download failures
- Netcup: network connectivity hint for API connection failure
- Scaleway: structured guidance for project ID lookup failure
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Extract _linode_extract_error helper to deduplicate 3 inline Python
error-extraction blocks, and _linode_handle_create_error to reduce
create_server from 47 to 31 lines.
Extract dispatchListCommand, dispatchSubcommand, dispatchVerbAlias, and
dispatchSlashNotation from the 63-line dispatchCommand function, reducing
it to 15 lines with clear single-responsibility helpers.
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Validate that cloud provider lib/common.sh files include actionable
error guidance when operations fail (destroy_server, create_server,
auth). Tests cover dashboard URLs, billing warnings, structured
logging, API error extraction, URL format, timeout messages, and
auth credential references across all providers.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SSRF fix in PR #948 added validate_region_name in create_server(),
but cloudsigma_api() is called much earlier via test_cloudsigma_credentials()
and cloudsigma_check_ssh_key(). A crafted CLOUDSIGMA_REGION (e.g.
"evil.com/foo#") could redirect API calls — including Base64-encoded
Basic Auth credentials — to an attacker's server before create_server()
is ever reached.
Move validation to get_cloudsigma_api_base() so every API call validates
the region before constructing the URL.
Also add a 10-digit length cap to the trigger-server issue parameter as
defense-in-depth against path traversal via absurdly long numbers in
worktree directory paths.
Fixes#960
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Direct unit tests for exported functions in commands.ts that were
previously only exercised through replicas or integration paths:
formatRelativeTime, formatTimestamp, getImplementedAgents,
getImplementedClouds, parseAuthEnvVars, hasCloudCredentials,
resolveDisplayName, buildRecordLabel, and buildRecordHint.
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
GCP create_server was 64 lines (largest function across all cloud libs).
Cherry create_server was 54 lines. Both are now under 30 lines each
by extracting focused helpers:
GCP (64 -> 25 lines):
- _gcp_prepare_instance_files: startup script + SSH key temp files
- _gcp_run_create: gcloud command execution with error diagnostics
- _gcp_get_instance_ip: IP extraction from instance describe
Cherry (54 -> 27 lines):
- _cherry_build_server_body: JSON payload construction
- _cherry_submit_create: API call with error handling
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Add log_install_failed helper to shared/common.sh that provides
structured troubleshooting for agent install failures: possible causes,
SSH debug command (when server IP available), manual install command,
and re-run suggestion. Also improve SSH key registration error message.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Improve error messages in cloud provider lib/common.sh files to include
specific troubleshooting steps, dashboard URLs, and environment variable
hints instead of bare "Failed" messages.
Providers improved: Netcup, IONOS, CloudSigma, Northflank, UpCloud,
Fly.io, RamNode, OVH, Civo, Scaleway.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds unit tests for buildCredentialStatusLines, formatAuthVarLine, and
the credential section allSet detection in showDryRunPreview. These
functions had zero direct test coverage despite being in the critical
dry-run preview path. Tests cover:
- formatAuthVarLine: env var set/missing display, URL hints, indentation
- buildCredentialStatusLines: OPENROUTER_API_KEY always present, single
and multi-var auth, URL hint placement, partial credentials, no-auth
clouds, all-set scenarios
- Dry-run allSet detection: all creds set, partial, multi-var, none auth
- credentialHints allSet branch: the "appear to be set" path when all
env vars are present but the error may be invalid/expired credentials
- credentialHints partial credentials: mixed set/missing env var states
Agent: test-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When server destruction fails, users are left with a bare error message and
no indication that they may still be billed for a running server. This adds
dashboard URLs and clear warnings to destroy_server errors across 9 clouds
(Hetzner, UpCloud, Contabo, Netcup, RamNode, Hostinger, HOSTKEY, OVH,
Latitude). Also improves error messages for Koyeb (app creation, service
deployment, deployment timeout, instance ID), GitHub Codespaces (creation
failure, readiness timeout), and E2B (sandbox creation failure).
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSH key registration in 11 cloud providers used unescaped key_name
directly in JSON request bodies. If the hostname (used to generate
key names) contained JSON-special characters like double-quotes, it
could break out of the JSON string and inject arbitrary JSON fields.
Fix: use json_escape for key_name in all providers, matching the
pattern already used by Scaleway.
Also fix GCP create_server which embedded the startup script inline
in --metadata with comma delimiters. Commas in the script could break
metadata parsing or inject additional metadata keys. Fix: use
--metadata-from-file for the startup script.
Affected providers: Hetzner, DigitalOcean, Vultr, BinaryLane,
Hostinger, Contabo, Cherry, HOSTKEY, Civo, Linode, Genesis Cloud, GCP.
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- GCP: capture gcloud stderr on failure, add common issues guidance,
use _log_diagnostic for ensure_gcloud errors
- AWS Lightsail: add common issues for create_server failure,
use _log_diagnostic for ensure_aws_cli errors,
improve instance timeout message with actionable steps
- Cherry Servers: use extract_api_error_message instead of raw response
dump, add common issues for server creation failure
- Oracle Cloud: capture OCI CLI stderr on instance launch failure,
add common issues for VCN, subnet, and instance creation errors
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove duplicate ensure_jq() function in shared/common.sh (lines 2341-2372)
that was accidentally left after extracting it to the shared lib in #946
- Move "Aliases: ls, history" onto the "spawn list" help line so it no longer
appears to describe "spawn list --clear"
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace execSync with execFileSync in reExecWithArgs() to prevent shell
metacharacter injection via binary path. execFileSync bypasses the shell
entirely, executing the binary directly with an argv array.
The performAutoUpdate() call retains execSync since it legitimately needs
a shell for piping (curl | bash).
Fixes#950
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CloudSigma UX fixes:
- Use log_error consistently for remediation hints (was log_warn)
- Add "Common issues" block to create_server failure
- Add actionable hints to server timeout error
- Extract API error message instead of dumping raw response
- Fix README: VNC password is random, not hardcoded
RamNode README:
- Update implemented agents list from 9 to all 15
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CloudSigma was the only cloud provider that did not validate its region
env var before interpolating it into the API base URL. A crafted
CLOUDSIGMA_REGION value (e.g. "evil.com/foo#") could redirect all API
calls — including HTTP Basic Auth credentials — to an attacker's server.
Adds validate_region_name check in create_server, matching the pattern
used by all other providers (DigitalOcean, Vultr, Hetzner, Fly, etc.).
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract `ensure_jq()` from hetzner and hostkey into shared/common.sh,
eliminating 64 lines of identical duplicated code
- Decompose DigitalOcean `create_server()` by extracting error handling
into `_do_check_create_error()` helper, and using the shared
`extract_api_error_message` instead of inline Python parsing
- Use shared `_extract_json_field` for droplet ID extraction
Agent: complexity-hunter
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: Add ramnode/codex script
Agent: gap-filler
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: address security review feedback for ramnode/codex
- Use inject_env_vars_ssh instead of raw heredoc (fixes command injection)
- Restore wait_for_cloud_init call after verify_server_connectivity
- Use .zshrc instead of .bashrc for consistency with other ramnode scripts
- Restore server info in success message
Agent: pr-maintainer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: B (Discovery Team) <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Two UX improvements:
1. Dry-run credential status now shows the cloud provider's URL next to
missing cloud-specific auth vars (e.g., HCLOUD_TOKEN), helping users
find where to create their credentials. Previously only
OPENROUTER_API_KEY showed a URL hint.
2. Added `spawn list --clear` command to let users clear their spawn
history. Previously there was no way to reset the 100-entry history
file without manually deleting ~/.spawn/history.json.
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>