Commit graph

2520 commits

Author SHA1 Message Date
A
15df9dfae3
fix(security): array-based agent detection and GCP instance name validation (#3158)
* fix(security): array-based agent detection and GCP instance name validation

Replace shell string concatenation in detectAgent() with individual
`command -v` calls per agent, eliminating the compound shell command.
Add _gcp_validate_instance_name() to validate GCP instance names match
[a-z][a-z0-9-]*[a-z0-9] before passing to gcloud commands.

Fixes #3151
Fixes #3149

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

* fix: add instance name validation in _gcp_cleanup_stale()

Defense-in-depth: validate instance names from GCP API before passing
to gcloud delete, consistent with validation at other call sites.

Agent: pr-maintainer
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-04-03 11:24:33 +07:00
A
e157637ab8
fix(e2e): add pi to E2E agent coverage (#3156)
Fixes #3152

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-03 10:15:43 +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
44940ceb5b
fix: update Hermes agent icon to Nous Research logo (#3150)
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 Hermes agent site now uses the Nous Research logo instead of the
old snake icon. Update our bundled asset to match.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 12:23:53 -07:00
A
7d593df09f
test: add coverage for validateAgentName and validateLocalPath (#3148)
These security-critical validation functions in local/local.ts had zero
direct test coverage. Adds tests for valid inputs, empty strings,
shell metacharacters, path traversal, and uppercase rejection.

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 01:52:52 +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
e3278578ee
fix(e2e): skip GCP tests when billing is disabled (#3146)
Add a billing pre-check to _gcp_validate_env so the E2E orchestrator
skips GCP gracefully ("skipped — credentials not configured") instead
of failing every agent individually when billing is disabled.

Fixes #3091

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 19:26:42 +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
4cf5e34383
fix: replace Pi agent icon with correct logo from shittycodingagent.ai (#3143)
Previous icon was a wrong GitHub avatar (Korean characters). Now uses
the official Pi logo (pixelated P with dot) from the project website.

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-04-02 00:28:34 -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
1dc5e43095
test: add coverage for validateScriptTemplate, resolveDisplayName, groupByType (#3140)
These three exported pure functions had zero test coverage. validateScriptTemplate
is security-critical (prevents ${} interpolation injection in script templates).

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-01 19:27:54 +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
cd9d12d2c4
fix(qa): add shutdown timeout and e2e-tester responsiveness to prevent infinite loop (#3134)
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
When the e2e-tester finishes work and goes idle without responding to
shutdown_request, the team lead retries indefinitely burning the entire
85-min budget in a shutdown loop.

Three fixes:
1. e2e-tester protocol: add explicit instruction to respond to
   shutdown_request immediately after reporting results
2. Step 4 shutdown sequence: add 60s timeout — if a teammate doesn't
   respond, proceed with TeamDelete anyway
3. Fix stale timeout reference (25/29/30 → 75/83/85 min)

Fixes #3093

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-01 13:43:09 +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
9895d6e8cc
fix: correct README agent count (10→9, 60→54) (#3131)
The Pi agent PR (#3128) bumped from 8→9 agents but the README tagline
was incorrectly set to "10 agents / 60 combinations" instead of matching
the manifest's actual 9 agents / 54 implemented entries.

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-01 10:11:51 +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
e98a3a5c4b
fix(e2e): use jq to count DigitalOcean droplets instead of grep (#3125)
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 previous grep -o '"id":[0-9]*' pattern matched all numeric id fields
in the droplets JSON response (including nested image/region/size ids),
overcounting droplets by 2x and falsely reporting quota exhaustion.

Replace with jq '.droplets | length' which correctly counts only top-level
droplet objects. This restores DigitalOcean capacity detection so e2e runs
can use available droplet slots.

-- qa/e2e-tester

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-31 16:32:33 +07:00
A
7f16619a7c
test: remove duplicate custom-flag test file (#3124)
custom-flag.test.ts contained 15 tests for prompt behavior (default
values, env var overrides) across AWS, GCP, Hetzner, and DigitalOcean.
Every one of these tests is an exact or near-exact duplicate of tests
already present in the cloud-specific coverage files:

- hetzner-cov.test.ts: promptServerType, promptLocation defaults + env vars
- gcp-cov.test.ts: promptMachineType, promptZone defaults + env vars
- do-cov.test.ts: promptDropletSize, promptDoRegion defaults + env vars
- aws-cov.test.ts: promptRegion, promptBundle env vars

No test coverage was lost — all scenarios remain in the cloud-specific
files with equal or greater assertion depth.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:55:24 +07:00
A
25690185a5
refactor: remove stale ZeroClaw references from CLAUDE.md and agents.ts (#3096)
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
* fix(ci): remove stale paths from biome check step in lint.yml

biome.json restricts linting to packages/**/*.ts via its includes filter,
so passing .claude/scripts/ and .claude/skills/setup-spa/ to the biome
check command was a no-op — biome reported 0 files processed for those
paths and silently skipped them.

Remove the stale paths so the CI step accurately reflects what biome
actually checks.

* 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>

* fix(digitalocean): use canonical DIGITALOCEAN_ACCESS_TOKEN env var (#3099)

Replaces all references to DO_API_TOKEN with DIGITALOCEAN_ACCESS_TOKEN,
matching DigitalOcean's official CLI and API documentation. This includes
TypeScript source, tests, shell scripts, Packer config, CI workflows,
and documentation.

Supersedes #3068 (rebased onto current main).

Agent: pr-maintainer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* 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>

* fix(cursor): set CURSOR_API_KEY to skip browser login (#3104)

Cursor CLI requires authentication before making API calls. Without
CURSOR_API_KEY set, it falls back to browser-based OAuth which fails
because the proxy spoofs api2.cursor.sh to localhost, breaking the
OAuth callback. Setting a dummy CURSOR_API_KEY makes Cursor use the
/auth/exchange_user_api_key endpoint instead, which the proxy already
handles with a fake JWT.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: sync README with source of truth (#3097)

- update tagline: 8 agents/48 combos -> 9 agents/54 combos
- add Cursor CLI row to matrix table

manifest.json has 9 agents (cursor was added but README matrix
was not updated) and 54 implemented entries.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>

* 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>

* 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>

* fix: add cursor to agent lists in spawn skill files (#3108)

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>

* 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>

* fix(manifest): correct cursor repo to cursor/cursor and update star counts (#3092)

The cursor agent's repo was set to anysphere/cursor (private, returns 404),
which caused the stars-update script to store the raw 404 error object as
github_stars instead of a number — breaking the manifest-type-contracts test.

Fix: update repo to the public cursor/cursor repo (32,526 stars as of 2026-03-29).
Also applies the daily star count updates for all other agents.

-- qa/e2e-tester

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>

* fix(spawn-fix): load API keys via config file, not just process.env (#3095)

Previously buildFixScript() resolved env templates directly from
process.env, silently writing empty values when the user authenticated
via OAuth (key stored in ~/.config/spawn/openrouter.json). Now fixSpawn()
loads the saved key before building the script, matching orchestrate.ts.

Fixes #3094

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: sync README commands table with help.ts (--prompt, --prompt-file) (#3106)

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>

* fix(e2e): reduce Hetzner batch parallelism from 3 to 2 (#3112)

Prevents server_limit_reached errors when pre-existing servers (e.g.
spawn-szil) consume quota during E2E batch 1.

Fixes #3111

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* refactor(e2e): normalize unused-arg comments in headless_env functions (#3113)

GCP, Sprite, and DigitalOcean had commented-out code `# local agent="$2"`
in their `_headless_env` functions. Hetzner already used the cleaner style
`# $2 = agent (unused but part of the interface)`. Normalize to match.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: Remove duplicate and theatrical tests (#3089)

* test: remove duplicate and theatrical tests

- update-check.test.ts: fix 3 tests using stale hardcoded version '0.2.3'
  (older than current 0.29.1) to use `pkg.version` so 'should not update
  when up to date' actually tests the current-version path correctly
- run-path-credential-display.test.ts: strengthen weak `toBeDefined()`
  assertion on digitalocean hint to `toContain('Simple cloud hosting')`,
  making it verify the actual fallback hint content

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

* test: replace theatrical no-assert tests with real assertions in recursive-spawn

Two tests in recursive-spawn.test.ts captured console.log output into a
logs array but never asserted against it. Both ended with a comment like
"should not throw" — meaning they only proved the function didn't crash,
not that it produced the right output.

- "shows empty message when no history": now spies on p.log.info and
  asserts cmdTree() emits "No spawn history found."
- "shows flat message when no parent-child relationships": now asserts
  cmdTree() emits "no parent-child relationships" via p.log.info.

expect() call count: 4831 to 4834 (+3 real assertions added).

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

* test: consolidate redundant describe block in cmd-fix-cov.test.ts

The file had two separate describe blocks with identical beforeEach/afterEach
boilerplate. The second block ("fixSpawn connection edge cases") contained only
one test ("shows success when fix script succeeds") and could be merged directly
into the first block ("fixSpawn (additional coverage)") without any loss of
coverage or setup fidelity.

Removes 23 lines of duplicated boilerplate. Test count unchanged (6 tests).

---------

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(config): extend biome.json includes to cover .claude/**/*.ts

Add .claude/**/*.ts to biome.json includes so TypeScript files in
.claude/scripts/ and .claude/skills/ are covered by biome formatting.
Linting is disabled for .claude/** via override because the GritQL
plugins (no-try-catch, no-typeof-string-number) target the main CLI
codebase and cannot be scoped per-path — .claude/ hook scripts
legitimately use try/catch as they run standalone outside the package.

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

* fix(prompts): stop infinite shutdown loop after TeamDelete in non-interactive mode (#3116)

After TeamDelete completes in -p (non-interactive) mode, Claude Code's
harness was re-injecting shutdown prompts every turn. The root cause:
the Monitor Loop instructed the agent to call TaskList + Bash on EVERY
iteration, including after TeamDelete, which kept the session alive so
the harness could inject more shutdown prompts.

Fix: add an explicit EXCEPTION to both refactor-team-prompt.md and
refactor-issue-prompt.md instructing the team lead that after TeamDelete
is called, the very next response MUST be plain text only with no tool
calls. A text-only response is the termination signal for the
non-interactive harness.

Fixes #3103

Agent: issue-fixer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix(zeroclaw): remove broken zeroclaw agent (repo 404) (#3107)

* fix(zeroclaw): remove broken zeroclaw agent (repo 404)

The zeroclaw-labs/zeroclaw GitHub repository returns 404 — all installs
fail. Remove zeroclaw entirely from the matrix: agent definition,
setup code, shell scripts, e2e tests, packer config, skill files,
and documentation.

Fixes #3102

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

* fix(zeroclaw): remove stale zeroclaw reference from discovery.md ARM agents list

Addresses security review on PR #3107 — the last remaining zeroclaw
reference in .claude/rules/discovery.md is now removed.

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

* fix(zeroclaw): remove remaining stale zeroclaw references from CI/packer

Remove zeroclaw from:
- .github/workflows/agent-tarballs.yml ARM build matrix
- .github/workflows/docker.yml agent matrix
- packer/digitalocean.pkr.hcl comment
- sh/e2e/e2e.sh comment

Addresses all 5 stale references flagged in security review of PR #3107.

Agent: issue-fixer
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>

* 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>

* fix(cli): allow --headless and --dry-run to be used together (#3118)

* test: remove redundant theatrical assertions (#3120)

Remove bare toHaveBeenCalled() checks that preceded stronger content
assertions, and strengthen the "shows manual install command" test to
verify the actual install script URL appears in output.

Affected files:
- cmd-update-cov: remove redundant consoleSpy.toHaveBeenCalled() (x2),
  strengthen "shows manual install command" to check install.sh content
- update-check: remove redundant consoleErrorSpy.toHaveBeenCalled() (x2)
  that were immediately followed by .mock.calls content assertions
- recursive-spawn: remove redundant logInfoSpy.toHaveBeenCalled() before
  content check
- cmd-interactive: remove redundant mockIntro/mockOutro.toHaveBeenCalled()
  before content checks

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: sync README tagline with manifest (9 agents/54 → 8 agents/48 combinations) (#3119)

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>

* docs: remove stale ZeroClaw references after agent removal (#3122)

ZeroClaw was removed in #3107 (repo 404). Two doc references were left
behind:
- .claude/rules/agent-default-models.md: table row for ZeroClaw model config
- README.md: ZeroClaw listed in --fast skip-cloud-init agent examples

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e): redirect DO max_parallel log_warn to stderr (#3110)

_digitalocean_max_parallel() called log_warn which writes colored output
to stdout, polluting the captured return value when invoked via
cloud_max=$(cloud_max_parallel). The downstream integer comparison
[ "${effective_parallel}" -gt "${cloud_max}" ] then fails with
'integer expression expected', silently leaving the droplet limit cap
unapplied. Fix: redirect log_warn output to stderr so only the numeric
value is captured.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>

* refactor: remove stale ZeroClaw references from docs and code comments

---------

Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
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>
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
2026-03-31 05:20:26 +00:00
A
455f4cd43e
fix(e2e): redirect DO max_parallel log_warn to stderr (#3110)
_digitalocean_max_parallel() called log_warn which writes colored output
to stdout, polluting the captured return value when invoked via
cloud_max=$(cloud_max_parallel). The downstream integer comparison
[ "${effective_parallel}" -gt "${cloud_max}" ] then fails with
'integer expression expected', silently leaving the droplet limit cap
unapplied. Fix: redirect log_warn output to stderr so only the numeric
value is captured.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-31 11:32:51 +07:00
A
54fc5f3ff3
fix(ci): remove stale paths from biome check, extend biome to .claude/ (#3123)
Remove .claude/scripts/ and .claude/skills/setup-spa/ from lint.yml biome step
(biome.json includes filter already excluded them — 0 files processed).

Add .claude/**/*.ts to biome.json includes with linter disabled override,
so .claude/ TypeScript gets formatting coverage without triggering GritQL
plugin violations (no-try-catch etc.) that don't apply to standalone hooks.

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-31 11:29:27 +07:00
A
447f669a9c
docs: remove stale ZeroClaw references after agent removal (#3122)
ZeroClaw was removed in #3107 (repo 404). Two doc references were left
behind:
- .claude/rules/agent-default-models.md: table row for ZeroClaw model config
- README.md: ZeroClaw listed in --fast skip-cloud-init agent examples

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-31 10:11:11 +07:00
A
551ce32424
docs: sync README tagline with manifest (9 agents/54 → 8 agents/48 combinations) (#3119)
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
2026-03-31 08:42:50 +07:00
A
e024900e38
test: remove redundant theatrical assertions (#3120)
Remove bare toHaveBeenCalled() checks that preceded stronger content
assertions, and strengthen the "shows manual install command" test to
verify the actual install script URL appears in output.

Affected files:
- cmd-update-cov: remove redundant consoleSpy.toHaveBeenCalled() (x2),
  strengthen "shows manual install command" to check install.sh content
- update-check: remove redundant consoleErrorSpy.toHaveBeenCalled() (x2)
  that were immediately followed by .mock.calls content assertions
- recursive-spawn: remove redundant logInfoSpy.toHaveBeenCalled() before
  content check
- cmd-interactive: remove redundant mockIntro/mockOutro.toHaveBeenCalled()
  before content checks

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 01:40:56 +00:00
A
a3ce5d8afd
fix(cli): allow --headless and --dry-run to be used together (#3118) 2026-03-31 00:38:44 +00: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
5e0144b645
fix(zeroclaw): remove broken zeroclaw agent (repo 404) (#3107)
* fix(zeroclaw): remove broken zeroclaw agent (repo 404)

The zeroclaw-labs/zeroclaw GitHub repository returns 404 — all installs
fail. Remove zeroclaw entirely from the matrix: agent definition,
setup code, shell scripts, e2e tests, packer config, skill files,
and documentation.

Fixes #3102

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

* fix(zeroclaw): remove stale zeroclaw reference from discovery.md ARM agents list

Addresses security review on PR #3107 — the last remaining zeroclaw
reference in .claude/rules/discovery.md is now removed.

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

* fix(zeroclaw): remove remaining stale zeroclaw references from CI/packer

Remove zeroclaw from:
- .github/workflows/agent-tarballs.yml ARM build matrix
- .github/workflows/docker.yml agent matrix
- packer/digitalocean.pkr.hcl comment
- sh/e2e/e2e.sh comment

Addresses all 5 stale references flagged in security review of PR #3107.

Agent: issue-fixer
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-30 15:35:40 -07:00
A
570caaba8d
fix(prompts): stop infinite shutdown loop after TeamDelete in non-interactive mode (#3116)
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
After TeamDelete completes in -p (non-interactive) mode, Claude Code's
harness was re-injecting shutdown prompts every turn. The root cause:
the Monitor Loop instructed the agent to call TaskList + Bash on EVERY
iteration, including after TeamDelete, which kept the session alive so
the harness could inject more shutdown prompts.

Fix: add an explicit EXCEPTION to both refactor-team-prompt.md and
refactor-issue-prompt.md instructing the team lead that after TeamDelete
is called, the very next response MUST be plain text only with no tool
calls. A text-only response is the termination signal for the
non-interactive harness.

Fixes #3103

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 04:58:09 +07:00
A
994a512115
test: Remove duplicate and theatrical tests (#3089)
* test: remove duplicate and theatrical tests

- update-check.test.ts: fix 3 tests using stale hardcoded version '0.2.3'
  (older than current 0.29.1) to use `pkg.version` so 'should not update
  when up to date' actually tests the current-version path correctly
- run-path-credential-display.test.ts: strengthen weak `toBeDefined()`
  assertion on digitalocean hint to `toContain('Simple cloud hosting')`,
  making it verify the actual fallback hint content

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

* test: replace theatrical no-assert tests with real assertions in recursive-spawn

Two tests in recursive-spawn.test.ts captured console.log output into a
logs array but never asserted against it. Both ended with a comment like
"should not throw" — meaning they only proved the function didn't crash,
not that it produced the right output.

- "shows empty message when no history": now spies on p.log.info and
  asserts cmdTree() emits "No spawn history found."
- "shows flat message when no parent-child relationships": now asserts
  cmdTree() emits "no parent-child relationships" via p.log.info.

expect() call count: 4831 to 4834 (+3 real assertions added).

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

* test: consolidate redundant describe block in cmd-fix-cov.test.ts

The file had two separate describe blocks with identical beforeEach/afterEach
boilerplate. The second block ("fixSpawn connection edge cases") contained only
one test ("shows success when fix script succeeds") and could be merged directly
into the first block ("fixSpawn (additional coverage)") without any loss of
coverage or setup fidelity.

Removes 23 lines of duplicated boilerplate. Test count unchanged (6 tests).

---------

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 13:59:55 -07:00
A
b0f9f4e7af
refactor(e2e): normalize unused-arg comments in headless_env functions (#3113)
GCP, Sprite, and DigitalOcean had commented-out code `# local agent="$2"`
in their `_headless_env` functions. Hetzner already used the cleaner style
`# $2 = agent (unused but part of the interface)`. Normalize to match.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 03:51:07 +07:00
A
f2f981bd0a
fix(e2e): reduce Hetzner batch parallelism from 3 to 2 (#3112)
Prevents server_limit_reached errors when pre-existing servers (e.g.
spawn-szil) consume quota during E2E batch 1.

Fixes #3111

Agent: test-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-31 03:08:18 +07:00
A
2077816b61
docs: sync README commands table with help.ts (--prompt, --prompt-file) (#3106)
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-31 03:05:56 +07:00
A
02cf129bc0
fix(spawn-fix): load API keys via config file, not just process.env (#3095)
Previously buildFixScript() resolved env templates directly from
process.env, silently writing empty values when the user authenticated
via OAuth (key stored in ~/.config/spawn/openrouter.json). Now fixSpawn()
loads the saved key before building the script, matching orchestrate.ts.

Fixes #3094

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-31 03:03:47 +07:00
A
52e78bdeb8
fix(manifest): correct cursor repo to cursor/cursor and update star counts (#3092)
The cursor agent's repo was set to anysphere/cursor (private, returns 404),
which caused the stars-update script to store the raw 404 error object as
github_stars instead of a number — breaking the manifest-type-contracts test.

Fix: update repo to the public cursor/cursor repo (32,526 stars as of 2026-03-29).
Also applies the daily star count updates for all other agents.

-- qa/e2e-tester

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
2026-03-31 03:00:24 +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
1378ed1c23
docs: sync README with source of truth (#3097)
- update tagline: 8 agents/48 combos -> 9 agents/54 combos
- add Cursor CLI row to matrix table

manifest.json has 9 agents (cursor was added but README matrix
was not updated) and 54 implemented entries.

Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Ahmed Abushagur <ahmed@abushagur.com>
2026-03-29 21:06:50 -07:00
A
9892355ede
fix(cursor): set CURSOR_API_KEY to skip browser login (#3104)
Cursor CLI requires authentication before making API calls. Without
CURSOR_API_KEY set, it falls back to browser-based OAuth which fails
because the proxy spoofs api2.cursor.sh to localhost, breaking the
OAuth callback. Setting a dummy CURSOR_API_KEY makes Cursor use the
/auth/exchange_user_api_key endpoint instead, which the proxy already
handles with a fake JWT.

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:05:26 -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
0bd8930c09
fix(digitalocean): use canonical DIGITALOCEAN_ACCESS_TOKEN env var (#3099)
Replaces all references to DO_API_TOKEN with DIGITALOCEAN_ACCESS_TOKEN,
matching DigitalOcean's official CLI and API documentation. This includes
TypeScript source, tests, shell scripts, Packer config, CI workflows,
and documentation.

Supersedes #3068 (rebased onto current main).

Agent: pr-maintainer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-30 08:48:56 +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
a29d0d8a15
fix(security): replace variable-stored shell code with named function in verify.sh (#3073)
Some checks are pending
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
Fixes #3070

The port_check / port_check_r variables stored executable shell code as
strings and expanded them via ${port_check} inside cloud_exec commands.
This is an eval-equivalent pattern: if any part of the variable were ever
derived from dynamic input, it would be directly exploitable as command
injection.

Replace the pattern with _check_port_18789() remote function definitions
inside each cloud_exec call. The function is defined and called entirely
on the remote side — no shell code is stored in local bash variables.

Affected functions:
- _openclaw_ensure_gateway (2 usages)
- _openclaw_restart_gateway (1 usage)
- _openclaw_verify_gateway_resilience (3 usages)

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-28 11:25:00 +07:00
A
4db068d0c4
fix(github-auth): add sudo availability check before use (#3072)
In rootless containers or environments without sudo, the script
previously failed with cryptic errors. Now fails fast with a clear
error message when non-root and sudo is unavailable.

Fixes #3069

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-28 08:39:22 +07:00