Commit graph

296 commits

Author SHA1 Message Date
A
bae921a295
fix(digitalocean): retry on 404 in waitForDropletActive (#2695)
DigitalOcean sometimes returns 404 immediately after droplet creation
before the resource propagates across their API. Previously this caused
an immediate fatal error, failing all DO agent provisions.

Now 404 responses are treated as transient and retried with the same
5s polling interval, consistent with how non-active statuses are handled.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-16 14:19:02 -07:00
A
085759aeaf
fix(security): add AWS secret key validation and harden path traversal (#2690)
- Add validateAwsSecretKey() function checking 40-char format
- Validate secret key in loadCredsFromConfig() and lightsailRest()
- Add normalize() to canonicalize paths before traversal check
- Harden both uploadFile() and downloadFile() path validation
- Update test fixtures with properly-formatted mock secret keys
- Add test for invalid secret key format rejection

Fixes #2686
Fixes #2687

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-16 01:29:01 -07:00
A
e5725b9a66
fix(gcp): add /usr/local/bin to .spawnrc PATH for npm-global agents (#2681)
GCP VMs install kilocode (and other npm-global agents) to /usr/local/bin
via `npm install -g`. The .spawnrc PATH export relied on $PATH inheriting
/usr/local/bin from the SSH/login shell chain, but on GCP VMs the PATH
can be minimal depending on how the session is initiated (login shell
sourcing order, /etc/profile.d availability). Explicitly include
/usr/local/bin to ensure npm globally-installed binaries are always
findable regardless of base PATH.

Also updates fix.ts to keep its PATH in sync with generateEnvConfig().

Fixes #2679

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 00:00:09 -07:00
A
09576f16ef
fix(ui): remove confusing "None" checkbox from setup options (#2682)
The "None" sentinel option stayed checked alongside real selections,
which was confusing. Remove it — the multiselect already supports
submitting with nothing selected via `required: false`.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 23:43:01 -07:00
A
5cc9930769
feat(cli): add spawn link command to reconnect existing deployments (#2675)
Adds `spawn link <ip>` command that re-registers an existing cloud VM
in spawn's local state, so commands like `spawn list`, `spawn delete`,
and `spawn fix` work on it without reprovisioning.

Features:
- Auto-detects running agent via SSH (ps aux + which checks)
- Auto-detects cloud provider via IMDS metadata endpoints (Hetzner,
  AWS, DigitalOcean, GCP)
- Accepts --agent, --cloud, --user, --name flags to skip auto-detection
- TCP connectivity pre-check before SSH attempts
- Creates a SpawnRecord in history with full connection info
- Offers to connect immediately after linking
- Interactive picker fallback when auto-detection fails
- Non-interactive mode support (exits with clear error if detection
  fails without --agent/--cloud flags)

Also adds --user / -u to KNOWN_FLAGS for the unknown-flag checker.

Fixes #2673

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-15 23:11:13 -07:00
Ahmed Abushagur
6ef20ed437
fix(aws): auto-select server size by agent (#2676)
* fix(aws): auto-select server size instead of prompting

OpenClaw gets 4GB (medium_3_0), all other agents get 2GB (small_3_0).
Users can still override with SPAWN_CUSTOM=1 or LIGHTSAIL_BUNDLE env var.
Matches the auto-select behavior already used by DO and Hetzner.

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

* feat: guide Windows users to WSL at startup

Detects win32 platform and prints step-by-step WSL setup instructions
instead of failing with a confusing error.

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

* Revert "feat: guide Windows users to WSL at startup"

This reverts commit 8db72880ae.

* test: update DEFAULT_BUNDLE assertion to small_3_0

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-15 23:08:41 -07:00
A
c4961eb5cd
fix(e2e): prevent concurrent history write race and fix GCP HOME env (#2678)
* fix(history): use process-unique tmp file to prevent concurrent write race

Multiple spawn processes running in parallel (e.g. during E2E tests with
--parallel 6) all write to the same history.json.tmp path, causing ENOENT
when one process renames the file before another can. Use a pid+timestamp
suffix so each process writes to its own unique tmp file.

Fixes provision crashes seen in hetzner-junie E2E runs where the fatal
"rename history.json.tmp -> history.json" error aborted the session.

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

* fix(gcp): export HOME=/root in startup script to match cloud-init behavior

DigitalOcean and Hetzner cloud-init scripts both set `export HOME=/root`
before running Node installation. GCP's startup script did not, which
could cause `n` (the Node.js version manager) to install Node to an
unexpected location when HOME is unset or points elsewhere.

Without a consistent HOME, `npm prefix -g` may return a path that doesn't
match what the subsequent `npm install -g @kilocode/cli` expects, causing
the install to fail silently and leaving the kilocode binary absent.

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

---------

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 23:06:23 -07:00
A
0ea2692e1e
fix(github-auth): always run gh setup when user explicitly opts in (#2674)
When the user selects the GitHub CLI step in setup options (interactive
prompt or --steps github), offerGithubAuth() was silently returning early
if no local gh token was found by detectGithubAuth(). This made the step
unreachable for users without gh installed locally — exactly the ones who
need remote setup most.

Fix: accept an `explicitlyRequested` parameter in offerGithubAuth(). When
true, skip the githubAuthRequested guard and always run the remote install.
The orchestrator passes enabledSteps?.has("github") as this flag.

detectGithubAuth() still auto-enables the step when a local token exists
(convenience forwarding), but can no longer block a user-explicit request.

Fixes #2672

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-15 22:19:38 -07:00
A
00df240f49
feat(openclaw): add channel selection to setup options (#2671)
Add BlueBubbles, Discord, Slack, Signal, and Google Chat to the
multi-select setup options for OpenClaw. Selected channels get
`enabled: true` stubs written via `openclaw config set`, so the
dashboard renders channel cards properly instead of showing
"Unsupported type: . Use Raw mode."

Channels are gated by enabledSteps — only user-selected channels
get stubbed. WhatsApp and Telegram remain in the list as before.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 20:21:42 -07:00
A
4f4b535c8d
fix(security): validate remotePath and harden base64 interpolation in uploadConfigFile (#2669)
Add strict character validation for remotePath to prevent command injection
via crafted paths. Use shellQuote for tempRemote in the shell command. Add
a base64 output assertion to document and enforce the safety of single-quoted
interpolation for settingsB64.

Fixes #2668

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-15 19:04:19 -07:00
A
52eaa19466
fix: allow empty string values for CLI flags like --steps "" (#2662)
extractFlagValue() used `!args[idx + 1]` to detect a missing value,
which treated empty strings as missing. Change to `=== undefined` so
that `--steps ""` passes through correctly as documented.

Fixes #2661

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-15 13:45:39 -07:00
Ahmed Abushagur
0a7a95ec3c
feat: add custom model selection to all agents (#2659)
Move "Custom model" from OpenClaw-specific to common setup steps so
every agent shows it in the setup menu. Add modelEnvVar to agents that
support model override via environment variable:

- Kilo Code: KILOCODE_MODEL
- ZeroClaw: ZEROCLAW_MODEL
- Hermes: LLM_MODEL
- Junie: JUNIE_MODEL

When a custom model is selected, the env var is injected into .spawnrc
alongside the other agent env vars. OpenClaw continues to use its
existing configure() path. Claude and Codex don't have modelEnvVar
since they handle model routing differently.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 12:44:48 -07:00
Ahmed Abushagur
bc2aa89002
fix: enable channel stubs so openclaw extensions load their schemas (#2658)
Channel extensions only register their UI schemas when enabled. With
enabled=false the dashboard still shows "Unsupported type: . Use Raw
mode." Setting enabled=true lets the extensions load so users can
configure channels from the dashboard.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 14:48:40 -04:00
Ahmed Abushagur
9ca71f2da7
fix: write channel stubs in openclaw config for dashboard rendering (#2657)
Write disabled telegram and whatsapp channel entries during setup so
the OpenClaw dashboard renders proper channel cards instead of showing
"Unsupported type: . Use Raw mode." Users can then configure channels
from the dashboard UI.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 10:56:42 -07:00
A
6cf748e1b5
feat(openclaw): use openclaw onboard --non-interactive instead of manual config JSON (#2655)
Replace the manual config JSON construction + download-merge-upload flow
with `openclaw onboard --non-interactive`, which creates a properly
structured config with auth profiles, provider setup, gateway config,
and workspace. Follow up with `openclaw config set` for browser and
Telegram settings.

This fixes the broken dashboard channel setup caused by bypassing
OpenClaw's credential/auth profile system. Removes the gateway auth
re-assertion hack that was needed due to field-dropping during
config set cycles on manually-written JSON.

Includes a fallback path that writes minimal JSON if onboard fails.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:44:37 -04:00
Ahmed Abushagur
05c7070396
fix: re-upload openclaw config after config set calls to preserve channels (#2649)
Each `openclaw config set` call does a read-modify-write that can drop
fields like channels and gateway auth. After all config set calls,
re-download the config, deep-merge our configObj on top, and re-upload
to restore any dropped fields.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 06:46:52 -04:00
A
245a2a46f9
feat: offer delete or remap when server is gone from cloud provider (#2641)
* feat: offer delete or remap when server is gone from cloud provider

When a user tries to connect to a server that no longer exists, instead
of silently marking it as deleted, present an interactive picker that
lets them remap the history entry to an existing instance on the same
cloud or explicitly remove it from history.

- Add listServers() to Hetzner, DigitalOcean, AWS, and GCP providers
- Add updateRecordConnection() to history for remapping server details
- Add handleGoneServer() interactive flow in list.ts
- Fall back to silent deletion in non-interactive mode (SPAWN_NON_INTERACTIVE)

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

* refactor: move InstancesListSchema to module level

Declare valibot schema at module top level per project convention,
not inside the listServers() function body.

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

* refactor: extract shared CloudInstance type from duplicated inline types

The { id, name, ip, status } shape was declared inline 9 times across
5 files. Extract it as a shared CloudInstance interface in history.ts
and import it in all cloud providers and list.ts.

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

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 17:05:51 -07:00
A
dc91b27431
feat(digitalocean): show account info on errors + offer to switch accounts (#2638)
When DO API calls fail (billing issues, locked account, droplet creation
errors), users may be logged into the wrong account. Now shows email/team/
status and offers to re-authenticate before giving up.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-14 16:36:21 -07:00
Ahmed Abushagur
cef7c69522
feat: rank agents by GitHub stars + add update-stars.sh (#2635)
Sort agent picker by github_stars descending so most popular agents
appear first. Add update-stars.sh script to QA quality sweep to keep
star counts fresh.

Security fixes from PR #2629 review:
- Validate repo format (owner/name pattern) before gh api calls
- Validate and canonicalize REPO_ROOT with realpath

Supersedes #2629.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-14 15:49:41 -07:00
A
0f9bbd399c
fix(digitalocean): catch billing 403 thrown by doApi on droplet creation (#2628)
doApi() throws on any non-2xx response before the isBillingError() check
at the call site could execute, making billing error detection dead code.

Wrap the POST /droplets call in asyncTryCatch so the thrown error message
(which includes the response body) is checked with isBillingError(). If it
matches a billing pattern, handleBillingError() is shown with the billing
page link and retry prompt — same UX as the proactive first-run warning.

Also adds a test asserting isBillingError() matches errors in the format
doApi throws (regression guard for #2395).

Fixes #2395

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-14 18:09:48 -04:00
Ahmed Abushagur
d435963dbc
fix: remove WhatsApp from setup, nothing pre-selected by default (#2626)
WhatsApp setup is too complex for normal users (QR scan + separate
device + pairing). Remove it from the setup options entirely.

Also change multiselect defaults to nothing pre-selected — let users
opt in to what they want instead of pre-selecting for them.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:10:28 -07:00
A
a738e658a3
feat: add separate Open Dashboard action in spawn ls menu (#2622)
Add "Open Dashboard" as its own menu item for agents with tunnel
metadata (e.g., OpenClaw). Establishes an SSH tunnel, opens the
browser with the auth token, and waits for Enter to close.

The menu now shows both options for dashboard agents:
  - Enter OpenClaw (launches TUI via SSH)
  - Open Dashboard (opens web UI in browser)

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 16:45:19 -04:00
A
c878e5b5d8
feat: persist tunnel metadata so spawn ls can re-establish dashboard proxy (#2620)
When an agent has an SSH tunnel (e.g., OpenClaw dashboard), store the
tunnel remote port and browser URL template in connection.metadata at
spawn time. On reconnect via `spawn ls` → "Enter agent", re-establish
the SSH tunnel and open the dashboard automatically.

- Add saveMetadata() to history.ts for merging key-value pairs into records
- Store tunnel_remote_port and tunnel_browser_url_template in orchestrate.ts
- Re-establish tunnel in cmdEnterAgent (connect.ts) when metadata is present

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 15:43:13 -04:00
A
689989005a
fix: reorder interactive menu — "Create" before "Connect" (#2619)
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 12:25:16 -07:00
A
3c11bf33d7
fix: tunnel gateway port 18789, not internal control service 18791 (#2618)
The OpenClaw dashboard (Control UI) is served by the Gateway on port
18789, which also handles WebSocket connections for agent communication.
Port 18791 is the internal Control Service — not the user-facing dashboard.

We were tunneling 18791, so the browser connected to the wrong service
and showed "Unauthorized" because the Control Service doesn't accept
token-based dashboard auth.

Fix: tunnel port 18789 (Gateway) and update all USER.md references.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 12:12:17 -07:00
A
c323f0e2e3
fix: openclaw dashboard auth — add gateway.auth.mode and use fragment token (#2617)
OpenClaw 2026.3.7+ requires an explicit `gateway.auth.mode: "token"` field
when `gateway.auth.token` is set. Without it the gateway rejects auth and the
dashboard shows "Unauthorized".

Additionally, pass the token via URL fragment (`#token=`) instead of query
parameter (`?token=`) to match the updated auth flow and avoid leaking the
token in server logs / Referer headers (GHSA-rchv-x836-w7xp).

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:48:34 -04:00
A
1195fcb648
fix: add timeout to sprite create subprocess to prevent indefinite hang (#2614)
The `sprite create` API call in `createSprite()` had no timeout, so when
the Sprite API blocked for certain agents (kilocode, opencode), the
process hung indefinitely. The bash-level timeout in provision.sh wraps
the outer subshell but the deeply-nested `sprite create` subprocess
could survive signal propagation.

Add a 300s (configurable via SPRITE_CREATE_TIMEOUT) timeout to the
`sprite create` subprocess using the existing killWithTimeout +
asyncTryCatch pattern already used by runSprite() and destroyServer().

Fixes #2612

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 08:17:55 -04:00
A
c4ce4a1b24
test: add coverage for spawn feedback command (#2609)
Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 05:46:35 -04:00
Ahmed Abushagur
9f51244cb2
fix: messaging UX — silence doctor, fix groupPolicy, drop early WhatsApp pairing (#2607)
* fix: messaging UX — silence doctor, fix groupPolicy, remove early WhatsApp pairing

- Set groupPolicy to "open" for both Telegram and WhatsApp (was
  "allowlist" with empty allowFrom, causing doctor warnings)
- Suppress doctor warning spam by redirecting openclaw config set
  stdout to /dev/null
- Remove WhatsApp pairing prompt (appeared immediately after QR scan
  before user could message the bot — now just tells them the command)

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

* fix: improve Telegram/WhatsApp pairing instructions

Add step-by-step instructions for Telegram pairing so users know to
search for their bot in Telegram and message it. Improve WhatsApp
post-link instructions to explain how contacts pair.

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

* feat: pre-select Telegram in setup options as recommended channel

Telegram has the smoothest setup UX (bot token + pairing code) compared
to WhatsApp (QR scan + separate device). Pre-select it alongside Chrome
in the multiselect and label it as "recommended" in the hint.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 03:46:19 -04:00
Ahmed Abushagur
ca5fe851cd
fix: proper Telegram/WhatsApp channel setup using config + pairing (#2605)
Telegram is a built-in channel, not a plugin. Replace broken
`openclaw plugins enable telegram` (OOM) and `openclaw channels add`
(doesn't exist) with proper setup:

- Write channel config (botToken, dmPolicy: pairing, groups) directly
  into the atomic JSON config file during setup
- After gateway starts, prompt user to pair via
  `openclaw pairing approve <channel> <CODE>`
- WhatsApp: QR scan via `openclaw channels login`, then pairing
- Bump version to 0.17.16

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 02:21:02 -04:00
A
f1f8b53dde
fix: prepend IS_SANDBOX and PATH exports in buildFixScript (#2604)
Fixes #2603

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 20:35:49 -07:00
Ahmed Abushagur
b3f221f5bd
fix: use openclaw onboard for channel setup (#2598)
* fix: set telegram groupPolicy to open during channel setup

OpenClaw defaults groupPolicy to "allowlist" with an empty groupAllowFrom,
which silently drops all group messages. Set it to "open" after adding the
Telegram channel so group messages work out of the box.

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

* fix: use OpenClaw config file for Telegram setup instead of broken CLI commands

Telegram is a built-in channel in OpenClaw, not a plugin. The previous
approach used `openclaw plugins enable telegram` (caused OOM on 2GB) and
`openclaw channels add --channel telegram` (command doesn't exist).

Now writes Telegram config (botToken, enabled, groupPolicy) directly into
the atomic JSON config file during setup. Also sets groupPolicy to "open"
so group messages work out of the box instead of being silently dropped.

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

* fix: use openclaw onboard for channel setup instead of manual config

OpenClaw has a built-in `openclaw onboard` command that interactively
guides users through Telegram/WhatsApp channel setup. Use that instead
of manually prompting for tokens and writing config ourselves.

- Remove custom Telegram token prompt from agent-setup.ts
- Remove broken `openclaw channels add` and `openclaw plugins enable`
- Run `openclaw onboard` after gateway starts for channel setup
- Base config (API key, gateway, model) still written atomically

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 18:45:16 -04:00
Ahmed Abushagur
0b5c702b71
fix: enforce minimum 4GB RAM for openclaw on DigitalOcean (#2597)
openclaw-plugins OOMs on s-2vcpu-2gb (2GB) droplets during config
loading. Auto-upgrade to s-2vcpu-4gb when no custom size is set.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:47:24 -04:00
Ahmed Abushagur
39622b68ab
feat: add --beta images for DO marketplace images (#2593)
* feat: add --beta images for DO marketplace images

Gate pre-built DigitalOcean marketplace images behind --beta images.
When active, uses hardcoded marketplace slugs (e.g. openrouter-spawnclaude)
instead of fresh Ubuntu + cloud-init, skipping agent install entirely.

All 8 images verified working via e2e smoke test (2026-03-13).

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

* fix: sort exports to satisfy biome organizeImports

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 15:45:25 -04:00
A
8f02646b4c
feat: add spawn feedback subcommand (#2585)
* feat: add `spawn feedback` subcommand

Sends anonymous feedback to the Spawn team via PostHog survey API.
Usage: spawn feedback "your message here"

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

* fix: update feedback survey ID and response key

Use the correct PostHog survey ID and $survey_response property.

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

* fix: use asyncTryCatch instead of try/catch in feedback command

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

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 10:19:37 -07:00
A
d1bbd6cac9
refactor: remove dead parameters from internal functions (#2581)
Remove 5 unused underscore-prefixed parameters that were accepted but
never read: extractFlagValue._flagLabel, performUpdate._remoteVersion,
reportDownloadFailure._primaryUrl/_fallbackUrl, buildRecordLabel._manifest,
and setupCodexConfig._apiKey. All callers updated accordingly.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-13 09:55:03 -07:00
A
cb0ed08da0
security: add shell quoting around TERM in cloud module commands (#2579)
Defense-in-depth: wrap sanitized TERM values in single quotes in all
four SSH-based cloud modules (aws, hetzner, digitalocean, gcp). The
allowlist in sanitizeTermValue() already prevents injection, but quoting
the interpolated value adds a second layer of protection.

Also extends test coverage with additional injection vectors (pipes,
redirects, variable expansion, empty strings) and a test verifying the
complete allowlist.

Fixes #2577

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-13 08:17:46 -04:00
A
13538cfa98
fix: re-assert gateway auth token after openclaw browser config set calls (#2571)
Each `openclaw config set` does a read-modify-write on the config file,
which can drop fields written by uploadConfigFile — including
gateway.auth.token. This caused the OpenClaw dashboard to return
"Unauthorized" on every fresh deploy.

Fix: after the browser config set and plugin enable blocks, re-set
gateway.auth.token via `openclaw config set` (same non-fatal pattern as
the existing Telegram token call), ensuring the token survives all
read-modify-write cycles.

Fixes #2570

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-13 04:17:34 -04:00
Ahmed Abushagur
6c8c098ba7
fix: enable OpenClaw channel plugins before configuring them (#2564)
Telegram and WhatsApp plugins are disabled by default in OpenClaw.
Setting a bot token without enabling the plugin causes the gateway
to hang on startup. Running `openclaw channels login --channel
whatsapp` without the plugin enabled fails with "Unsupported channel".

Now runs `openclaw plugins enable telegram/whatsapp` before any
channel configuration. Also adds step-by-step instructions for
getting a Telegram bot token from @BotFather.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-13 03:50:22 -04:00
A
6839e34395
fix: remove duplicate --model flag from help and error output (#2562)
The --model flag was listed twice in two user-facing outputs:
- help.ts USAGE section: lines 11 and 20 both showed --model <id>
  with different descriptions
- index.ts unknown-flag error: lines 118 and 121 both showed --model
  with different descriptions

Both duplicates were introduced when --model support was added.
Combined the two entries into one clear line each.

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-13 02:50:36 -04:00
A
d578e614e2
refactor: remove dead HeadlessOptions re-export from commands barrel (#2560)
HeadlessOptions is defined and used internally in commands/run.ts but
re-exported from commands/index.ts with no consumer — index.ts imports
cmdRunHeadless but passes options inline without importing the type.
This is a CLI binary, not a library, so unused re-exports add surface
area without value.

Also move the run.ts comment to be adjacent to the run.ts exports.

Bump CLI version to 0.17.4.

-- qa/code-quality

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-12 22:37:55 -07:00
Ahmed Abushagur
8a5908acd2
fix: add step-by-step instructions for getting a Telegram bot token (#2558)
New users don't know how to get a bot token. Show instructions
before the prompt: open @BotFather, send /newbot, copy the token.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:03:13 -04:00
A
ecc876f3bc
fix: remove dead shellQuote re-export from gcp/gcp.ts (#2551)
Dead backwards-compat re-export left over from the shellQuote
consolidation (PRs #2533, #2535, #2546). Zero consumers import
shellQuote from gcp/gcp.ts — all correctly import from shared/ui.ts.
Per CLAUDE.md: avoid backwards-compatibility hacks; delete unused code.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-12 21:42:09 -04:00
A
2b83a8106d
security: use shellQuote() in agent-setup.ts for consistent null-byte defense (#2546)
Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-12 19:44:50 -04:00
Ahmed Abushagur
d2d71b17ef
feat: add --model flag and preferences file for LLM model override (#2543)
Adds --model / -m CLI flag to override the agent's default LLM model:
  spawn codex gcp --model openai/gpt-5.3-codex

Also supports persistent per-agent model preferences via config file at
~/.config/spawn/preferences.json:
  { "models": { "codex": "openai/gpt-5.3-codex" } }

Priority: --model flag > preferences file > agent default.

This enables a future web UI to pass model selection via CLI args when
invoking spawn programmatically to provision machines.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-12 18:47:09 -04:00
A
f6f36cc452
security: add DO_CLIENT_SECRET env var override (#2538)
* security: add DO_CLIENT_SECRET env var override

Allows users/organizations to supply their own DigitalOcean OAuth
client secret via DO_CLIENT_SECRET env var rather than relying on
the bundled default. The bundled secret remains as fallback.

Fixes #2537

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

* chore: bump CLI version to 0.16.19

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

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-12 15:48:36 -04:00
A
dfd08ad48c
security: consolidate shellQuote across all clouds (defense-in-depth) (#2535)
PR #2533 hardened GCP with shellQuote() and null-byte rejection, but
left Hetzner, DigitalOcean, AWS, and connect.ts using inline
.replace(/'/g, "'\\''") without null-byte validation.

- Move shellQuote to shared/ui.ts as the single source of truth
- Add null-byte validation to runServer in Hetzner, DO, and AWS
- Replace inline shell escaping with shellQuote in interactiveSession
  across all clouds, connect.ts, and agents.ts buildEnvBlock
- Re-export shellQuote from gcp.ts for backwards compatibility

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-12 12:54:31 -04:00
A
868ebbe4fe
security: harden shellQuote and consolidate shell escaping in gcp.ts (#2533)
- Add null-byte rejection to shellQuote (defense-in-depth)
- Export shellQuote for testability
- Refactor interactiveSession to use shellQuote instead of inline escaping
- Add comprehensive test suite for shellQuote security properties

Fixes #2529

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-12 10:27:48 -04:00
A
7278638a31
security: validate localPath in uploadFile() and harden runServer() in gcp.ts (#2524)
Fixes #2521 - Add path traversal and argument injection protection for localPath
Fixes #2522 - Add validation for cmd parameter before SSH execution

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-12 04:50:56 -04:00
Ahmed Abushagur
553cbad7bf
fix: revert OpenClaw default model to openrouter/auto (#2509)
OpenClaw requires the openrouter/ provider prefix for model IDs.
The previous default (moonshotai/kimi-k2.5) was missing the prefix,
causing "Unknown model" warnings. Reverted to openrouter/openrouter/auto
which uses OpenRouter's auto-router to pick the best model per prompt.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-12 01:06:50 -04:00