Commit graph

305 commits

Author SHA1 Message Date
Ahmed Abushagur
3aa34f21d3
feat(telemetry): use PostHog Error Tracking with $exception events (#3245)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
* feat(telemetry): use PostHog Error Tracking with $exception events

Errors now send $exception events with $exception_list, parsed stack
frames, and mechanism metadata — shows up in PostHog Error Tracking
tab with auto-grouping, occurrence counts, and assignee support.
Warnings stay as custom cli_warning events in Activity.

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

* fix: remove stderr monkey-patch, restore explicit capture calls

Remove process.stderr.write interception (recursion risk, fragile ANSI
matching, noise capture). Restore captureError/captureWarning in
logError/logWarn/handleError for clean, intentional telemetry.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
2026-04-09 00:52:57 -07:00
Ahmed Abushagur
2b99be70d1
fix(telemetry): move distinct_id into properties for PostHog batch API (#3243)
PostHog's /batch/ endpoint requires distinct_id inside each event's
properties object, not at the event level. Events were silently dropped.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:43:13 -07:00
Ahmed Abushagur
656b0da975
feat: add PostHog telemetry for CLI errors and warnings (#3242)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Sends CLI errors, warnings, and crashes to PostHog for observability.
Strictly error/warning events — no command tracking or session events.

All messages are scrubbed before sending:
- API keys (sk-or-v1-*, sk-ant-*, key-*)
- GitHub tokens (ghp_*, github_pat_*)
- Bearer tokens
- Email addresses
- IP addresses
- Long tokens (60+ char alphanumeric)
- Base64 blobs (40+ chars)
- Home directory paths (/Users/name → ~/[USER])

Default on. Disable with SPAWN_TELEMETRY=0.
Fire-and-forget with 5s timeout — never blocks the CLI.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:02:39 -07:00
Ahmed Abushagur
3c77825e6b
fix(openclaw): always set model after onboard to prevent wrong default (#3236)
`openclaw onboard --non-interactive` now defaults to arcee/trinity-large-thinking
instead of using the OpenRouter provider. Always run `openclaw config set
agents.defaults.model.primary` after onboard to ensure openrouter/auto is set.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:45:44 -07:00
Ahmed Abushagur
ad9da53210
feat(security): behavioral miner detection + spawn status security column (#3227)
Adds two behavioral crypto miner checks to the security scan:
- Flag non-agent processes using >80% CPU (catches renamed miners)
- Detect outbound connections to known mining pool ports (3333, 4444, etc.)

Adds a Security column to `spawn status` that shows clean/alerts/—
for each running server, with detailed alert summary after the table.
JSON output includes security and security_alerts fields.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 07:40:14 +07:00
A
52550dbdca
fix(security): replace eval-style interpolation with env var in allowOpenClawPreviewOrigin (#3217)
Pass the preview origin via SPAWN_PREVIEW_ORIGIN env var instead of
interpolating it into the Node.js inline script, preventing potential
command injection if a malicious preview URL were returned by the API.

Fixes #3215

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-06 23:09:45 -07:00
A
714c29c5a6
feat(gcp): add gh CLI to VM startup script (#3208)
Install GitHub CLI (gh) via the official APT repository in the GCP
cloud-init startup script, so it's available before SSH is reported
as ready. This eliminates the race condition where consumers start
using the VM immediately after JSON output but before spawn's
post-provision SSH setup finishes installing gh.

Fixes #3206

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-07 00:51:15 +07:00
A
606e521f33
fix: complete VM recovery rewrite for spawn fix command (#3178)
* fix: complete VM recovery rewrite for spawn fix command

Fixes #3173

Rewrites spawn fix to use CloudRunner interface for full VM recovery
instead of a flat bash script piped over SSH. Now runs the same
install(), configure(), preLaunch() functions as initial provisioning.

- Added generic SSH CloudRunner (ssh-runner.ts) reusable by other commands
- Exported injectEnvVarsToRunner() from orchestrate.ts for shared use
- Fixed command injection vulnerability via validateIdentifier(binaryName)
- Updated dependency injection: runScript → makeRunner (CloudRunner)
- Updated tests to use CloudRunner-based DI pattern

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

* test(ssh-runner): add coverage for validation paths

Tests cover the early-exit branches in makeSshRunner methods
(runServer invalid command, uploadFile/downloadFile path traversal)
that throw before any subprocess is spawned.

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

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 11:27:47 +07:00
A
df06bc85af
feat: headless promptCmd, link in cloud picker, default headless steps (#3177) 2026-04-05 01:39:39 +00:00
Muhammad Hashmi
a60d238dfc
fix(daytona): set per-sandbox user/org defaults (#3175)
* feat(daytona): re-add Daytona cloud provider

* fix(daytona): tighten live provider behavior

* fix(daytona): harden reconnect and dashboard flows

* fix(daytona): use platform sandbox defaults

* fix(daytona): add user and org defaults

* fix(ux): stop echoing shell script on startup

---------

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
2026-04-04 18:08:40 -07:00
Ahmed Abushagur
7797906241
fix(openclaw): remove blocking telegram pairing prompt (#3171)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
* fix(openclaw): fix telegram bot not responding to messages

The switch to `openclaw config set` calls in #2655 created malformed
nested config structures — the bot token and dmPolicy weren't read
properly by openclaw, so the bot never started polling for messages.
The `groups` block was also dropped entirely.

Fix: write the complete telegram channel object atomically via a bun
script that reads the existing config, deep-merges the full telegram
block, and writes it back — matching the original atomic JSON approach.

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

* fix(security): pass telegram config via env var instead of JS interpolation

Prevents JavaScript code injection via attacker-controlled bot token by
passing the telegramConfig JSON through a shell-quoted environment variable
(TELEGRAM_CONFIG) and parsing it with JSON.parse(process.env.TELEGRAM_CONFIG)
inside the bun script, instead of interpolating it directly into JS source.

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

* test: add test for atomic telegram config write

Verifies that openclaw telegram config uses a bun merge script (atomic
write) instead of individual `openclaw config set` calls, and that the
full config object (botToken, dmPolicy, groupPolicy, groups) is included.

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

---------

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-04-04 17:02:43 -07:00
A
78ebce9af8
fix: add pi agent and daytona cloud to embedded skill lists (#3172)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
The SKILL_BODY and HERMES_SNIPPET in spawn-skill.ts listed available
agents and clouds but were not updated when pi (#3156) and daytona
(#3168) were added. Agents spawned via the skill system could not
delegate work to Pi or provision on Daytona.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:52:05 +07:00
A
8e9af23c63
refactor: extract recordSpawn() helper to deduplicate spawn record construction (#3163)
The same 12-line saveSpawnRecord block was duplicated 3 times in
runOrchestration() (fast-mode boot, fast-mode retry, sequential path).
A bug fixed in one copy could easily be missed in another. Extracted
a shared recordSpawn() helper that all 3 sites now call.

Agent: complexity-hunter

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 07:46:57 +07:00
Muhammad Hashmi
9b176cd5b8
feat(daytona): add Daytona provider (#3168)
* feat(daytona): re-add Daytona cloud provider

* fix(daytona): tighten live provider behavior

* fix(daytona): harden reconnect and dashboard flows
2026-04-04 00:36:38 +00:00
A
0ffa035e35
fix(security): add command validation to local provider's runLocal/interactiveSession (#3160)
The local provider was missing the empty-string and null-byte command
validation that all other cloud providers (AWS, GCP, Hetzner, DO, Sprite)
already enforce. While callers currently pass hardcoded commands, this adds
defense-in-depth parity with the rest of the codebase.

Fixes #3155

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 12:18:04 +07:00
A
b99a16616f
fix(test): check sensitive paths before lstat to fix macOS permission error (#3157)
On macOS, lstat("/etc/master.passwd") throws EACCES before the
sensitive-path pattern check runs. Move pattern matching before
filesystem calls so security errors are thrown consistently
regardless of filesystem permissions.

Fixes #3153

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-03 10:12:20 +07:00
A
82a9939a80
fix(ux): interactive feedback prompt and link SSH error handling (#3147)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
- spawn feedback: prompt interactively for message when run in a TTY
  without arguments, instead of showing an error
- spawn link: report SSH failure after "Connect now?" instead of
  silently ignoring the exit code

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-02 20:38:52 +07:00
A
70bba831f6
fix(security): use array-based spawn for docker commands in local.ts (#3145)
Replace string-interpolated shell commands in pullAndStartContainer()
with Bun.spawn() array arguments, eliminating shell interpretation
as defense-in-depth against command injection.

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-02 16:33:42 +07:00
A
0c4dc613b2
fix(security): sanitize control characters in prompt file error messages (#3141)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Reject file paths containing ASCII control characters (ANSI escape
sequences, null bytes, etc.) in validatePromptFilePath() to prevent
terminal injection. Also strip control chars in handlePromptFileError()
as defense-in-depth for error paths before validation.

Fixes #3138

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-01 20:38:43 +07:00
A
d61cf02b9b
fix(security): validate paths and agent names to prevent traversal/injection (#3139)
Fixes #3136 - add path validation to uploadFile/downloadFile in local.ts
Fixes #3135 - add agentName validation before Docker shell commands

- validateLocalPath() resolves paths and rejects ".." traversal attempts
- validateAgentName() ensures agent names match [a-z0-9-]+ before Docker ops
- Both functions are exported for testability

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-01 11:28:03 +00:00
A
41f6b6eb8f
fix(cli): add --flat to KNOWN_FLAGS so spawn list --flat works (#3137)
The --flat flag was documented in help output and used by `spawn list`
but missing from KNOWN_FLAGS, causing an "Unknown flag" error.

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-01 16:33:45 +07:00
A
1599444517
fix(sandbox): use Docker runner for agent.configure() in sandbox mode (#3133)
Agent config functions (setupClaudeCodeConfig, setupCodexConfig, etc.)
captured the bare host runner from local/agents.ts, bypassing the Docker
wrapper. This caused config files like ~/.claude/settings.json to be
written to the host filesystem instead of inside the sandbox container.

Fix: when --beta sandbox is active, recreate agents with the Docker-wrapped
runner so configure()/install() closures execute inside the container.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 22:16:52 -07:00
A
3b61c22f25
fix(security): validate script templates before base64 encoding (#3132)
Add pre-encoding validation to reject ${} interpolation patterns in
script template strings before they are base64-encoded and injected
into systemd services running with root privileges on remote VMs.

Defense-in-depth against future regressions where template variable
interpolation before encoding could allow command injection.

Fixes #3130

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-01 10:15:20 +07:00
A
426ebc9b76
fix: start Docker daemon on sandbox startup, not just after install (#3129)
The sandbox mode now starts the Docker daemon whenever it's not running,
not only after a fresh install. This handles the common case where
OrbStack/Docker is installed but the daemon isn't started yet.

Flow: check daemon → if down, check binary → if missing, install →
start daemon (open -a OrbStack / systemctl start docker) → poll up to 30s

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:50:57 -07:00
A
c1d8acb73e
feat: add Pi coding agent (shittycodingagent.ai) to spawn (#3128)
Pi is a minimal terminal coding agent by Mario Zechner (~29.8k GitHub
stars) that natively supports OpenRouter via OPENROUTER_API_KEY.
Installed via npm as @mariozechner/pi-coding-agent, CLI command is `pi`.

- Add Pi agent config across all 6 clouds (local, hetzner, aws, do, gcp, sprite)
- Add manifest.json entry with matrix entries
- Add agent-setup.ts config (node cloudInitTier, npm install)
- Add spawn-skill.ts injection path (~/.pi/agent/skills/spawn/SKILL.md)
- Add bash wrappers for all clouds
- Update README matrix (also adds missing Cursor CLI row: 10 agents, 60 combos)

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:34:34 -07:00
A
14ea507313
feat: add --beta sandbox for Docker-based local agent sandboxing (#3127)
* feat: add --beta sandbox for Docker-based local agent sandboxing

When running agents locally, users can now opt into sandboxed execution
via `--beta sandbox` or the interactive picker. This runs the agent
inside a Docker container (using pre-built ghcr.io/openrouterteam images)
with memory and CPU limits, providing filesystem/network isolation.

- Docker auto-installed if missing (OrbStack on macOS, docker.io on Linux)
- Reuses existing makeDockerRunner() pattern from Hetzner/GCP
- Container auto-cleaned up on process exit
- OpenClaw security warning skipped in sandbox mode (already isolated)
- Interactive picker shows Direct vs Sandboxed when Docker available

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

* fix: rename local machine to local

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>

* fix: remove memory limits and move sandbox to cloud picker

- Remove --memory=4g --cpus=2 from docker run (breaks small VMs and recursive spawns)
- Replace sandbox sub-prompt with a "Local Machine (Sandboxed)" option
  in the cloud picker itself, shown when --beta sandbox is active
- Docker availability check happens later in local/main.ts (ensureDocker),
  not in the picker — so the option always appears with --beta sandbox

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

* docs: add --beta sandbox to README

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

---------

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
2026-03-31 17:00:49 -07:00
A
2b43996f60
fix(cli): allow --headless and --dry-run to be used together (#3117)
Removes the mutual-exclusion validation that blocked combining these flags.
Both flags serve independent purposes: --dry-run previews what would happen,
--headless suppresses interactive prompts and emits structured output.
Combining them is valid for CI pipelines that want structured JSON previews.

Fixes #3114

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-31 06:48:54 +07:00
A
9624141844
fix(security): expand $HOME before path validation in downloadFile (#3080)
Fixes #3080

Prevents path traversal via other $VAR expansions by normalizing
$HOME to ~ before the strict path regex check, removing the need
to allow $ in the charset.

Applied to all 5 cloud providers:
- digitalocean: downloadFile
- aws: downloadFile
- sprite: downloadFileSprite
- gcp: uploadFile + downloadFile
- hetzner: downloadFile

Also bumps CLI version to 0.27.7.

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:56:05 +00:00
A
ccbe52ccc2
fix: add cursor to agent lists in spawn skill files (#3108)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
cursor is a fully implemented agent across all 6 clouds but was missing
from the available agents list in spawn skill instructions injected onto
child VMs. This caused claude, codex, hermes, junie, kilocode, openclaw,
opencode, and zeroclaw to be unaware they could delegate work to cursor.

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
2026-03-29 22:49:04 -07:00
A
749f79a9c2
feat(status): add agent alive probe via SSH (#3109)
`spawn status` now probes running servers by SSHing in and running
`{agent} --version` to verify the agent binary is installed and
executable. Results show in a new "Probe" column (live/down/—) and
as `agent_alive` in JSON output. Only "running" servers are probed;
gone/stopped/unknown servers are skipped.

The probe function is injectable via opts for testability.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:44:46 -07:00
A
ddce16a438
fix(cursor): update proxy model list to current models (#3105)
Replace outdated models (Claude Sonnet 4, GPT-4o) with current ones:
- Claude Sonnet 4.6 (default), Claude Haiku 4.5
- GPT-4.1
- Gemini 2.5 Pro, Gemini 2.5 Flash

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:25:58 -07:00
A
b73761897a
fix: remove --trust flag from Cursor CLI launch command (#3101)
Cursor CLI v2026.03.25 only allows --trust in headless/print mode.
Launching interactively with --trust causes immediate exit with error.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
2026-03-29 20:46:39 -07:00
A
b9473f25b8
feat: add OpenRouter proxy for Cursor CLI agent (#3100)
Cursor CLI uses a proprietary ConnectRPC/protobuf protocol with BiDi
streaming over HTTP/2. It validates API keys against Cursor's own servers
and hardcodes api2.cursor.sh for agent streaming — making direct
OpenRouter integration impossible.

This adds a local translation proxy that intercepts Cursor's protocol
and routes LLM traffic through OpenRouter:

Architecture:
  Cursor CLI → Caddy (HTTPS/H2, port 443) → split routing:
    /agent.v1.AgentService/* → H2C Node.js (BiDi streaming → OpenRouter)
    everything else → HTTP/1.1 Node.js (fake auth, models, config)

Key components:
- cursor-proxy.ts: proxy scripts + deployment functions
- Caddy reverse proxy for TLS + HTTP/2 termination
- /etc/hosts spoofing to intercept api2.cursor.sh
- Hand-rolled protobuf codec for AgentServerMessage format
- SSE stream translation (OpenRouter → ConnectRPC protobuf frames)

Proto schemas reverse-engineered from Cursor CLI binary v2026.03.25:
- AgentServerMessage.InteractionUpdate.TextDeltaUpdate.text
- agent.v1.ModelDetails (model_id, display_model_id, display_name)
- TurnEndedUpdate (input_tokens, output_tokens)

Tested end-to-end on Sprite VM: Cursor CLI printed proxy response with
EXIT=0.

Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:59:00 -07:00
Ahmed Abushagur
ccd86005ce
fix: scope local warning to openclaw-only + improve spawn skill docs (#3074)
Some checks failed
CLI Release / Build and release CLI (push) Has been cancelled
Lint / ShellCheck (push) Has been cancelled
Lint / Biome Lint (push) Has been cancelled
Lint / macOS Compatibility (push) Has been cancelled
- Revert local security warning to openclaw-only (was blocking all agents)
- Update spawn skill to document how to run prompts on child VMs:
  - Always use `bash -lc` (binaries in ~/.local/bin/ need login shell)
  - Claude uses `-p` not `--print` or `--headless`
  - Add `--dangerously-skip-permissions` for unattended child VMs
  - Don't waste tokens with `which`/`find` or creating non-root users
- Sync all on-disk skill files with embedded version

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:54:24 -07:00
A
11f0c334aa
fix(digitalocean): fail fast when droplet quota is exhausted, list existing droplets (#3062)
Some checks failed
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Build Docker Images / build (claude) (push) Has been cancelled
Build Docker Images / build (codex) (push) Has been cancelled
Build Docker Images / build (cursor) (push) Has been cancelled
Build Docker Images / build (hermes) (push) Has been cancelled
Build Docker Images / build (junie) (push) Has been cancelled
Build Docker Images / build (kilocode) (push) Has been cancelled
Build Docker Images / build (openclaw) (push) Has been cancelled
Build Docker Images / build (opencode) (push) Has been cancelled
Build Docker Images / build (zeroclaw) (push) Has been cancelled
- E2E: _digitalocean_max_parallel() now returns 0 (not 1) when no capacity
- E2E: run_agents_for_cloud() skips cloud with actionable error when capacity is 0
- CLI: checkAccountStatus() includes droplet names in limit-reached error message

Fixes #3059

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-27 18:49:18 +07:00
A
db77121414
fix: reject disabled agents in CLI validation instead of silently proceeding (#3061)
resolveEntityKey() and checkEntity() checked manifest.agents[input] directly,
bypassing the disabled filter in agentKeys(). This let users run `spawn cursor
<cloud>` even though cursor is disabled, wasting time provisioning a VM for an
agent that can't route through OpenRouter. Now both functions check the disabled
flag and show the disabled_reason to the user.

Also removes stale cursor references from spawn skill templates injected into
child VMs.

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-27 10:22:18 +00:00
A
0bca96af58
fix(local): show security warning for all local agent installations (#3060)
Previously the warning only appeared for openclaw. Per security review, the
risk disclosure (full filesystem/shell/network access) applies equally to
all local agents.

Agent: pr-maintainer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:24:15 +07:00
Ahmed Abushagur
e0dca0cad9
fix: add child VM usage tips to spawn skill to prevent token waste (#3054)
The skill now documents that --headless only provisions (doesn't run
the prompt), that agent binaries are at ~/.local/bin/ (not on PATH),
and that --print should be used for one-shot prompts as root instead
of fighting with permission restrictions.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:33:56 +07:00
A
f685374567
fix(security): use uploadConfigFile for config deployment, chmod 600 openclaw config (#3038)
Replace base64-into-shell interpolation with SCP-based uploadConfigFile()
for Claude Code settings.json and Cursor CLI config files. This eliminates
the attack surface of injecting encoded payloads into shell command strings.

Add chmod 600 on ~/.openclaw/openclaw.json after writing the Telegram bot
token to prevent other users on the VM from reading the token in plaintext.

Fixes #3033
Fixes #3034

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-27 06:15:03 +07:00
A
4ac4a7e0cf
feat: recursive spawn tree passback (#3023)
* feat: pull child spawn history back to parent for `spawn tree`

When the interactive session ends (or headless mode completes), the
parent downloads the child VM's history.json and merges records into
local history. Before downloading, it runs `spawn pull-history` on the
child, which recursively pulls from all grandchildren — so the full
tree collapses up to the root regardless of depth.

Changes:
- Add getParentFields() — sets parent_id/depth on saveSpawnRecord calls
- Add pullChildHistory() — downloads + merges child history after session
- Add `spawn pull-history` command for recursive SSH-based history pull
- Add 11 tests for parseAndMergeChildHistory

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

* chore: trigger CI recompute

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

* fix(security): validate user/ip params before SSH exec in pull-history

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

* fix(security): use shared validators for SSH params in pull-history and delete

Replace inline regex checks in pull-history.ts with validateUsername()
and validateConnectionIP() from security.ts, matching the pattern used
across connect.ts, fix.ts, and link.ts. Also add the same validation
to delete.ts:pullChildHistory which had no SSH parameter validation.

orchestrate.ts uses the runner abstraction (not raw user@ip), so its
SSH params come from the cloud provider, not untrusted history records.

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

---------

Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
2026-03-26 15:21:50 -07:00
A
f2044f8d62
fix: add --yes/-y to KNOWN_FLAGS so spawn delete --name <name> --yes works (#3024)
PR #3015 added --yes and -y flags to the delete command but didn't add
them to KNOWN_FLAGS in flags.ts. This caused `spawn delete --name foo --yes`
to fail with "Unknown flag: --yes" because checkUnknownFlags runs before
dispatchDeleteCommand strips these flags.

Also adds delete-specific flags to --help documentation.

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-27 02:54:33 +07:00
Ahmed Abushagur
0f48e4dae5
feat: headless delete via spawn delete --name <name> --yes (#3015)
Agents running on spawned VMs couldn't delete child spawns because
`spawn delete` requires an interactive terminal for the picker UI.

Added --name and --yes flags: when both are provided in non-interactive
mode, the server matching the name is deleted without prompts. This
enables agents to manage their own child VMs programmatically.

Updated all skill files to teach agents the headless delete syntax.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: A <258483684+la14-1@users.noreply.github.com>
2026-03-26 12:30:15 -07:00
A
405dbc6ba6
refactor: use getSpawnCloudConfigPath(), remove dead _cloudName param (#3010) (#3012)
Replace hand-constructed openrouter.json path with getSpawnCloudConfigPath("openrouter")
for single-source-of-truth path resolution. Remove unused _cloudName parameter since
the function delegates ALL cloud credentials unconditionally.

Agent: ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 19:26:09 +07:00
A
fd36ff0e3d
fix(security): add base64 validation guards in orchestrate.ts (fixes #3006) (#3007)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Add /^[A-Za-z0-9+/=]+$/ validation after each .toString("base64") call
in delegateCloudCredentials() and injectEnvVars(), consistent with the
pattern established in agent-setup.ts by #2988.

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:25:40 +07:00
A
52d06c4cb5
fix: resolve ANSI spinner corruption and garbled output (#3001) (#3003)
* fix(ux): replace download spinner with stderr logging, reset terminal before SSH handoff

Fixes two UX issues from live E2E session (#3001):

1. Download spinner (p.spinner from @clack/prompts) wrote ANSI escape codes
   to stdout. When stdout is captured (E2E harness, piped output), these
   sequences appeared as raw text rather than rendered colors. Replace
   p.spinner() in downloadScriptWithFallback and downloadBundle with
   logStep/logInfo/logError from shared/ui.ts, which write to stderr and
   correctly check isTTY before emitting ANSI codes.

2. Garbled output at start of interactive session (overlapping status lines
   from the remote agent's TUI) may be caused by residual ANSI state from
   @clack/prompts (hidden cursor, active color attributes). Emit
   ESC[?25h ESC[0m to stderr before prepareStdinForHandoff() to explicitly
   restore cursor visibility and reset all attributes before the SSH session
   takes over.

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

* fix: resolve ANSI spinner corruption and garbled output in interactive mode (#3001)

Three root causes fixed:

1. Spinner wrote to stdout while all other CLI status output goes to stderr,
   causing ANSI escape sequence interleaving and corruption when both streams
   are merged on a terminal. Redirected all p.spinner() calls to process.stderr.

2. unicode-detect.ts (which sets TERM=linux for SSH sessions to force ASCII
   fallback) was only imported in commands/shared.ts but not in shared/ui.ts.
   Cloud module entry points (hetzner/main.ts, etc.) that import shared/ui.ts
   loaded @clack/prompts without the TERM override, causing Unicode spinner
   frames in environments that can't render them.

3. After an interactive SSH session ends, the remote agent's TUI (e.g. Claude
   Code) may leave the terminal in raw mode with altered attributes. Added
   terminal reset (ANSI attribute reset + stty sane) after spawnInteractive()
   returns to prevent garbled post-session output.

Agent: ux-engineer
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.6 <noreply@anthropic.com>
2026-03-26 15:28:32 +07:00
A
ff7315202e
fix: add missing --beta parallel and --beta recursive to --help text (#2990)
The CLI help output only listed 3 of 5 beta features (tarball, images,
docker). The error output on invalid beta flags and the README both
correctly listed all 5. This adds the missing parallel and recursive
entries to --help for consistency.

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-26 10:10:52 +07:00
A
a7f3e9da82
refactor: remove dead code and stale references (#2996)
- Remove `export` from `getTerminalWidth` in commands/info.ts — only
  used internally, not exported from commands/index.ts barrel
- Remove `export` from `makeDockerExec` in shared/orchestrate.ts — only
  used internally by `makeDockerRunner`, no external callers
- Bump CLI version to 0.26.6

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-26 08:40:42 +07:00
Ahmed Abushagur
90dde882d0
fix: installSpawnCli fails on Sprite — bun shim doesn't work (#2993)
Sprite has a bun shim at /.sprite/bin/bun that delegates to
$HOME/.bun/bin/bun, but that binary doesn't exist on fresh VMs.
`command -v bun` returns true (finds the shim) so the install script
skips bun installation, then bun fails when actually invoked.

Fixed in two places:
- installSpawnCli: source shell profiles, test `bun --version` (not
  just existence), and install bun fresh if it doesn't work
- install.sh: replace `command -v bun` with `bun --version` to detect
  broken shims

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:36:12 +07:00
Ahmed Abushagur
b47d6bbe1d
fix: embed skill content instead of reading from disk (#2992)
* fix: spawn step skipped when no explicit --steps passed

The spawn skill injection condition used `enabledSteps?.has("spawn")`
which is falsy when enabledSteps is undefined (no --steps flag). Now
checks the recursive beta flag directly and falls through when no
explicit steps are selected, matching how auto-update works.

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

* fix: embed skill content in spawn-skill.ts instead of reading from disk

The skills/ directory exists in the repo but isn't bundled when the CLI
is installed via npm. readSkillContent() couldn't find the files at
runtime, causing "No spawn skill file for agent" on every deploy.

Fixed by embedding all skill content directly as string constants in the
module. Removed fs-based getSkillsDir/readSkillContent/getSpawnSkillSourceFile
in favor of a single AGENT_SKILLS config map with inline content.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 06:16:52 +07:00
A
82ab6d35dc
fix(security): add base64 validation for all shell-interpolated values (#2988)
Previously only `settingsB64` had a validation check. Added the same
`/^[A-Za-z0-9+/=]+$/` guard for wrapperB64, unitB64, and timerB64
before they are interpolated into shell commands, closing the consistency gap.

Fixes #2986

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 05:16:02 +07:00