Remove 18 redundant/theatrical tests from unknown-flags.test.ts:
- Removed duplicate 'should detect --verbose as unknown' test (same name,
same assertion, nearly identical inputs as the test 28 lines above it)
- Consolidated 14 individual 'allows known flags' tests — each called
findUnknownFlag([flag]) with a single flag and expected null — into one
data-driven loop over all 17 flags; same coverage, 13 fewer test cases
- Removed 'should contain --name flag' which is fully subsumed by the
immediately following 'should contain all expected flags' test that
already verifies --name along with 22 other flags
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The hermes agent was added to manifest.json and sh/sprite/hermes.sh in
feat #2023, but createAgents() in shared/agent-setup.ts was not updated.
This caused sh/sprite/hermes.sh to throw "Unknown agent: hermes" when
resolveAgent() was called.
- Add hermes entry to createAgents() with correct install cmd, envVars, and launchCmd
- Update sprite/main.ts usage error message to include hermes
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Hermes Agent on Sprite cloud. Hermes is a persistent AI
agent by Nous Research with multi-platform messaging (Telegram,
Discord, Slack, CLI), memory across sessions, tool use, and native
OpenRouter support.
- Add hermes agent entry to manifest.json with env config
- Add matrix entries for all 7 clouds (sprite implemented, rest missing)
- Create sh/sprite/hermes.sh thin bash shim
Closes#1952
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace eval "${cloud_env}" with a while-read loop that only
processes lines matching ^export VAR="VALUE" — arbitrary shell
commands in cloud driver output are silently ignored.
Also removes the now-unused cloud_env local variable since the
while-read loop calls cloud_headless_env directly.
Fixes#2019
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace bash -c "${cmd}" with bash -c '$1' _ "${cmd}" so the
command is passed as a positional argument, not interpolated into
the shell string. Same pattern applied to the timeout wrapper.
Fixes#2018
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove 162 lines of duplicate/theatrical test code across 5 files:
- manifest-integrity.test.ts: Drop 5 crude toBeTruthy() field checks
superseded by manifest-type-contracts.test.ts with precise type assertions
- manifest-type-contracts.test.ts: Remove dead deps block (0 agents have
deps) and Dotenv configuration describe block (0 agents have dotenv) —
both generated zero tests at runtime
- commands-error-paths.test.ts: Remove 5-test "swapped arguments detection"
block duplicated by the more thorough commands-swap-resolve.test.ts
- run-path-credential-display.test.ts: Remove 5-test "implementation checks"
block that retested getImplementedClouds/Agents already covered by
commands-exported-utils.test.ts; also remove now-unused imports
- manifest.test.ts: Remove redundant toBeDefined() assertion after an
already-present toHaveProperty() check on the same field
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove stale comments in test files that referenced deleted test files
(commands-untested.test.ts, commands-helpers.test.ts) and remove
"Agent: X" metadata annotations that became obsolete after the
theatrical test cleanup.
All 1424 tests pass, biome lint clean.
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
* feat: SPAWN_CLI_DIR env var to force local source in e2e and shell scripts
When SPAWN_CLI_DIR is set, the entire toolchain uses local TypeScript
source instead of downloading pre-bundled scripts from GitHub releases:
- e2e.sh: auto-sets SPAWN_CLI_DIR to repo root when running locally
- provision.sh: exports SPAWN_CLI_DIR into the headless subshell
- commands.ts: reads local shell scripts instead of fetching from CDN
- All 36 cloud/agent shell scripts: exec local main.ts when set
This enables e2e tests to validate local changes before they're released.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): add path traversal defense to SPAWN_CLI_DIR script loading
Canonicalize the path via realpathSync and verify it stays inside the
resolved CLI directory before reading. Prevents SPAWN_CLI_DIR from
being used to read arbitrary files via ../ traversal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): harden SPAWN_CLI_DIR path traversal defense
- Validate cloud/agent names don't contain '..', '/' or '\' before
constructing file paths
- Fix root-directory edge case in prefix check by handling trailing
separator correctly
Agent: pr-maintainer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Remove 3 duplicate tests from security-edge-cases.test.ts that were
already covered in security.test.ts:
- validateIdentifier: "reject 65-char identifier" (duplicate of
security.test.ts "should reject overly long identifiers")
- validateScriptContent: "accept wget|sh" (duplicate of
security.test.ts "should accept scripts with wget|bash")
- validatePrompt: "accept prompts at exactly the max length" (duplicate
of security.test.ts "should accept prompts at the size limit")
The edge-cases file retains unique tests: 64-char boundary check,
single char identifiers, mkfs with multiple filesystems, extra-whitespace
pipe detection, etc.
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>
E2E tests were failing because agent installs didn't complete within
the default 120s timeout, and small VMs ran out of memory during builds.
- INSTALL_WAIT: 120s → 300s (with per-cloud override via cloud_install_wait)
- AWS: nano_3_0 → medium_3_0 (all agents need 4GB for reliable installs)
- DigitalOcean: s-1vcpu-512mb-10gb → s-2vcpu-2gb, cap at 3 parallel
- GCP: e2-medium → e2-standard-2
- Hetzner: cap at 5 parallel (primary IP limit)
- Sprite: 300s install wait (slower exec than SSH)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
When multiple clouds run in parallel, they generate the same app name
(e.g. e2e-claude-TIMESTAMP) and write to the same temp files
(.exit/.stdout/.stderr), causing data corruption.
- Include ACTIVE_CLOUD in make_app_name: e2e-gcp-claude-TIMESTAMP
- Use ${app_name} instead of ${agent} for provision temp files
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
* test: Remove duplicate and theatrical tests
Found and removed three categories of test anti-patterns:
1. **Conditional always-pass guard** (`with-retry-result.test.ts`): The
`if (!result.ok)` wrapper around `expect(result.error).toBeInstanceOf(Error)`
was silently skippable if the condition ever evaluates false. Replaced with a
type-narrowing early return (`if (result.ok) { return; }`) so the assertions
always execute when the code path is reached.
2. **Duplicate `loadManifest` tests** (`manifest.test.ts`): Five tests covering
stale-cache fallback, no-cache-network-fail, invalid-fetch-fallback, fetch
timeout, and cached-instance reuse were exact duplicates of tests already
in `manifest-cache-lifecycle.test.ts` (which covers these scenarios more
thoroughly). Removed the duplicates; kept the three tests unique to
`manifest.test.ts` (fetch URL validation, fresh-cache skips network,
forceRefresh behavior).
3. **Duplicate manifest structural checks** (`manifest-type-contracts.test.ts`):
The "Cross-referential consistency" (matrix coverage, no-invalid-refs, valid
status values) and "Display name uniqueness" (unique agent/cloud names, no
key collisions) describe blocks duplicated tests already present in
`manifest-integrity.test.ts`. Removed the 6 redundant tests; the unique type
validation tests (per-field `typeof` checks) remain.
Net result: -11 tests, 0 new failures, lint clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: biome format issues in test files
Remove trailing blank line in manifest.test.ts and expand single-line
if block in with-retry-result.test.ts to satisfy biome formatter.
Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
When a user selects a Daytona sandbox in `spawn list` and chooses
"SSH into VM", cmdConnect was missing the daytona-sandbox sentinel
handler. It fell through to the generic SSH path and tried to run
`ssh daytona@daytona-sandbox`, which fails with a cryptic error.
Added the handler mirroring the existing pattern in cmdEnterAgent.
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
`gcloud compute instances create` doesn't accept `--subnet-region`.
The subnet region is automatically inferred from `--zone`. This flag
causes all GCP provisioning to fail.
Bump CLI to 0.11.3.
Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Dead code removed:
- `cleanup_stale_apps` function in `sh/e2e/lib/cleanup.sh` — defined but
never called; `e2e.sh` calls `cloud_cleanup_stale` directly instead
- `generateEnvConfig` and `AgentConfig` re-exports from all 7 cloud-specific
`agents.ts` modules (aws, hetzner, gcp, digitalocean, daytona, local,
sprite) — nothing imported these from the cloud modules; they were already
available via `@openrouter/spawn-shared` and `../shared/agents`
All 1435 tests pass, biome lint is clean (0 errors).
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete manifest-helpers.test.ts: all 5 describe blocks (isValidManifest,
readCache corruption, matrixStatus edge cases, countImplemented case
sensitivity, agentKeys/cloudKeys) are fully covered by the more
comprehensive manifest-cache-lifecycle.test.ts
- Fix always-pass assertion in manifest.test.ts: replaced
expect(typeof existsSync(...)).toBe("boolean") (always true) with
expect(manifest.agents).toBeDefined() which actually tests the outcome
-- qa/dedup-scanner
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
* feat(e2e): multi-cloud test suite with cloud driver pattern
Scale the E2E test suite from AWS-only to all 6 infrastructure clouds
(aws, hetzner, digitalocean, gcp, daytona, sprite) with parallel
execution support.
Architecture:
- Cloud driver pattern: each cloud implements _cloudname_func() functions
- load_cloud_driver() wires cloud-specific functions to generic names
(cloud_exec, cloud_teardown, etc.)
- Shared orchestration stays in one place, cloud details are isolated
New files:
- sh/e2e/e2e.sh — unified entry point with --cloud flag
- sh/e2e/lib/clouds/{aws,hetzner,digitalocean,gcp,daytona,sprite}.sh
Refactored:
- common.sh — removed AWS constants, added load_cloud_driver()
- provision.sh — cloud-agnostic via cloud_headless_env/cloud_provision_verify
- verify.sh — replaced aws_ssh with cloud_exec/cloud_exec_long
- teardown.sh/cleanup.sh — delegate to cloud driver functions
- aws-e2e.sh — thin wrapper: exec e2e.sh --cloud aws
Usage:
e2e.sh --cloud aws # Single cloud
e2e.sh --cloud aws --cloud hetzner # Multiple clouds in parallel
e2e.sh --cloud all --parallel 3 # All clouds, 3 agents parallel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(e2e): prevent subshell EXIT trap inheritance and single-cloud early exit
- Reset EXIT trap in multi-cloud subshells to prevent LOG_DIR deletion
before the main process reads log files
- Use `|| true` for single-cloud run_agents_for_cloud to prevent set -e
from skipping the summary on env validation failure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: default to parallel agent provisioning in e2e tests
All agents within a cloud now run in parallel by default instead of
sequentially. Use --sequential to restore the old behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cap sprite parallelism, 4GB for openclaw, remove stderr suppression
- Sprite: add _sprite_max_parallel (cap 2 concurrent agents) to avoid
CLI rate limiting that caused all 6 agents to fail
- AWS: use medium_3_0 (4GB) bundle for openclaw which needs more RAM
- Input tests: remove 2>/dev/null from agent commands so failures
produce visible error output instead of empty responses
- Add cloud_max_parallel to driver interface, respected by e2e.sh
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use bash instead of sh for exec_long across all cloud drivers
Ubuntu's /bin/sh is dash, which doesn't support bash-specific PATH
sourcing from .spawnrc/.cargo/env. This caused codex and zeroclaw
input tests to fail with "command not found" even though verify passed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: codex input test uses positional prompt, not -q flag
codex CLI takes prompt as positional arg: `codex "PROMPT"`.
The -q flag doesn't exist, causing "Usage:" error output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use codex exec -q for non-interactive input test
codex requires `exec` subcommand for non-interactive mode.
Plain `codex PROMPT` expects a TTY (stdin is not a terminal).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: codex exec takes no -q flag, just positional prompt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use cx23 instead of deprecated cx22 for Hetzner e2e tests
Hetzner deprecated server type cx22 (ID 104). The default now uses cx23.
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>
Remove `setupOpenclawBatched` from `packages/cli/src/shared/agent-setup.ts`.
This function was exported but never called anywhere in the codebase — it was
superseded by the composable `setupOpenclawConfig` + `startGateway` approach
used in `createAgents()`.
Bump CLI patch version to 0.11.7.
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(security): validate launch_cmd from history before shell execution
launch_cmd from history.json was passed directly to bash -lc via SSH with no
validation, enabling command injection if the history file was tampered with.
Adds validateLaunchCmd() that blocks $(...), backticks, pipes, command chaining,
redirections, and variable expansion. Validation is applied at both merge time
(history.ts:mergeLastConnection) and execution time (commands.ts:cmdEnterAgent).
Fixes#2005
Agent: security-auditor
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* style: apply biome formatting to security.ts
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>
- Remove `runLocalCapture` from local/local.ts (exported but never called)
- Remove `listServers` from aws, hetzner, digitalocean, daytona modules
(all exported but never imported or called anywhere)
- Remove `InstanceListSchema` from aws.ts (only used in removed listServers)
- Remove now-unused imports in daytona.ts (parseJsonRaw, toObjectArray, toRecord)
- Bump CLI version 0.11.4 → 0.11.5
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>
* test: Remove duplicate and theatrical tests
- Remove duplicate `prioritizeCloudsByCredentials with real-world patterns`
describe block from run-path-credential-display.test.ts (84 lines). All
four of its scenarios were already covered by the primary
`prioritizeCloudsByCredentials` describe block in the same file.
- Remove theatrical `SPAWN_CUSTOM env var propagation` describe block from
custom-flag.test.ts (21 lines). Its two tests only verified that
`process.env` assignment works, not any application code.
No test scenarios lost; pre-existing 64 failures are unchanged.
-- qa/dedup-scanner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style: fix trailing blank lines in test files for biome format
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>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
QA bots (quality, fixtures) were creating PRs as drafts but never
marking them ready with `gh pr ready`. This caused the security bot
to close them as "stale drafts" — even when they were fresh and
contained valid work (e.g. PR #1974, closed at 2 hours old).
The fix is simple: don't create drafts. QA bots already run tests
before opening PRs, so the draft→ready dance is unnecessary. The
security bot reviews and merges non-draft PRs directly.
Changed files:
- qa-quality-prompt.md: all 4 teammates now open non-draft PRs;
safety rule updated to explain why
- qa-fixtures-prompt.md: removed --draft flag from gh pr create
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
spawn delete hetzner was silently returning "No active servers to delete"
even when the user had active Hetzner servers. The positional arg was
parsed as agentFilter, but no agent is named "hetzner", so the filter
matched nothing. cmdList already calls resolveListFilters() which
auto-promotes a bare arg to cloudFilter when no agent matches — cmdDelete
was missing this call entirely.
Agent: ux-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(security): restrict file permissions on history and config directories
History files (history.json, last-connection.json) were created with
default permissions (0644), making server IPs, usernames, and cloud
provider details readable by other local users on shared systems.
Config directories (~/.config/spawn/) were created via mkdir -p with
default umask (0755), making them world-listable.
- Add mode: 0o600 to all writeFileSync calls in history.ts
- Add mode: 0o700 to mkdirSync calls for ~/.spawn/ directory
- Replace Bun.spawn(["mkdir","-p",...]) with mkdirSync({mode:0o700})
in hetzner, aws, digitalocean, and daytona modules
Agent: security-auditor
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* style: fix Biome formatting for multiline object literals
Expand inline `{ mode: 0o600 }` and `{ recursive: true, mode: 0o700 }`
to multiline format to satisfy Biome's formatter.
Agent: pr-maintainer
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.5 <noreply@anthropic.com>
* refactor: extract shared TTY scaffolding in picker.ts
pickToTTYWithActions (248 lines) and multiPickToTTY (204 lines) shared
~120 lines of identical TTY lifecycle code: open /dev/tty, save/restore
stty settings, raw mode, write helper, restore helper, key-read buffer,
and the read loop skeleton.
Extract withTTYKeyLoop<T>() that owns the entire TTY lifecycle and
delegates rendering and key handling via callbacks. Both picker functions
now focus solely on their mode-specific logic.
Net: 672 -> 561 lines (-111), with TTY management in a single place.
Agent: complexity-hunter
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* style: apply biome formatting to picker.ts
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>
- cmdlist-integration.test.ts: removed references to list-display.test.ts,
list-table-rendering.test.ts, list-empty-footer.test.ts,
list-filter-suggestions.test.ts, and list-prompt-display.test.ts (none exist)
- history.test.ts: removed stale formatTimestamp reference; the function was
removed from production code, leaving the describe block name misleading
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(security): prevent flag injection via hyphen-leading path segments in uploadFile
Reject remote paths where any segment starts with "-" (e.g., "-e", "/tmp/-evil")
across all 6 cloud providers. This prevents potential CLI flag injection in
commands like base64, printf, mv, and scp.
Agent: security-auditor
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* style: fix biome format for path validation conditions
Break long if-conditions across multiple lines and add parentheses
around arrow function parameters to satisfy biome formatter.
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>
* test: remove duplicate listing command tests from commands-display.test.ts
The cmdMatrix, cmdAgents, cmdClouds, and related edge-case tests were
duplicated between commands-display.test.ts and cmd-listing-output.test.ts.
The listing-output file provides more thorough end-to-end coverage with
grid/compact view testing, type grouping, cross-command consistency checks,
and better edge case coverage. Remove the weaker duplicates (21 tests) from
commands-display.test.ts, keeping the unique cmdAgentInfo and cmdHelp tests.
-- qa/dedup-scanner
* style: fix trailing blank line in commands-display.test.ts
Remove extra blank line before closing brace that caused Biome format check failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The security bot was closing draft PRs that were only hours old,
claiming "no updates for 7+ days." Two root causes:
1. Step 1 said "skip draft PRs" but Step 3.5 referenced "the PR list
from Step 1" to find drafts — contradictory instructions meant the
bot either had no drafts to check or applied stale logic to
non-drafts.
2. Step 3.5 had no explicit date arithmetic — just "updatedAt older
than 7 days" which the bot interpreted loosely without verification.
Fix: Step 1 now saves the full list (drafts included) for Step 3.5.
Step 3.5 now requires mandatory date arithmetic (epoch comparison) and
draft/non-draft timeline checks before closing. Safety section also
strengthened with explicit draft age requirement.
Fixes the false positive that closed PR #1974 (2 hours old).
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Remove `formatTimestamp` from commands.ts: exported but never imported
or called anywhere in the codebase
- Remove `mockFetchWithStatus` from test-helpers.ts: exported but never
imported by any test file
- Remove `createProcessExitMock` from test-helpers.ts: exported but
never imported by any test file
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: remove false-confidence do-oauth.test.ts
This file imported zero functions/constants from the DigitalOcean
source module. Every test re-created private constants (tokenRegex,
codeRegex, DO_SCOPES, SUCCESS_HTML, etc.) inline and tested them
against themselves. If source code changed, these tests still passed
silently — providing false confidence worse than no tests.
Per CLAUDE.md: "If a function is not exported, do NOT test it
(don't re-implement it inline)."
Agent: test-engineer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: remove plan file from commit
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>
Add `|| remotePath.includes("..")` check to hetzner, digitalocean,
and aws uploadFile functions. The regex `/^[a-zA-Z0-9/_.~-]+$/`
allows `.` characters, so paths like `../../etc/passwd` pass the
regex but are path traversal attempts. gcp, daytona, and sprite
already include this explicit check — this makes all providers
consistent.
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Add is_safe_branch_name() validation to all four cycle scripts
(discovery.sh, refactor.sh, qa.sh, security.sh) to reject branch
names containing shell metacharacters before passing them to git
push --delete or git branch -D. Also adds -- end-of-options
separator to all git branch commands to prevent flag injection.
Fixes#1960
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* test: Remove duplicate and theatrical tests
- cmd-listing-output: Fix always-pass guard (if (localLine) → expect defined then check)
- with-retry-result: Replace conditional if (!r.ok) expects with toMatchObject
- run-path-credential-display: Remove 96 lines of duplicate tests
- parseAuthEnvVars for credential status (duplicate of commands-exported-utils.test.ts)
- credential function edge cases with weak OR assertions (duplicate of credential-hints.test.ts)
- Migrated 2 unique edge cases (whitespace trimming, empty separator) to commands-exported-utils.test.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: apply biome format to test files in qa/dedup-scanner
Agent: team-lead
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
The provision.sh was setting wrong env var names that the TypeScript CLI
does not read:
- AWS_LIGHTSAIL_INSTANCE_NAME → LIGHTSAIL_SERVER_NAME (read by aws.ts:getServerName)
- AWS_REGION → AWS_DEFAULT_REGION (read by aws.ts:authenticate/promptRegion)
- AWS_BUNDLE → LIGHTSAIL_BUNDLE (read by aws.ts:promptBundle)
Without the correct names, each provisioning run created an instance with a
random generated name instead of app_name, causing the post-provision
existence check to fail every time.
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove stale Fly.io references from shared shell scripts. Fly.io was
removed as a cloud provider (#1979) but comments referencing its
specific token format ("FlyV1 <macaroon>") and container behavior
remained in key-request.sh and github-auth.sh.
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(hetzner): update deprecated cx22/cpx21 server types to cx23/cpx22
Hetzner deprecated the entire cx*2 and cpx*1 server lines on Jan 1, 2026.
New orders fail with "server type is deprecated". Updates to the current
gen3 CX and gen2 CPX lines (cx23, cx33, cx43, cx53, cpx22, cpx32).
Also shows the server type picker by default instead of requiring --custom,
so users can choose their instance size on every deploy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(zeroclaw): append autonomy config instead of overwriting onboard output
zeroclaw onboard generates a complete config with required fields like
default_temperature. Our setup was overwriting that with a partial config
missing required fields, causing a crash loop on startup. Now appends
the security/shell settings instead so onboard's fields are preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: fix biome formatting in agent-setup.ts
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Agent: pr-maintainer
---------
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
DigitalOcean's token exchange endpoint requires client_secret and does
not support PKCE-only public client flows. The embedded secret follows
the same pattern used by gh CLI, doctl, gcloud, and az CLI. Expanded
the comment to explain:
- Why client_secret is required (no PKCE support)
- Why embedding it is acceptable (public client, RFC 6749 §2.1)
- What security mechanisms are actually relied upon
- When the secret should be removed (if DO adds PKCE)
Fixes#1980
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(aws): increase OpenClaw gateway timeout to 120s and default to medium bundle
OpenClaw gateway consistently times out on AWS Lightsail because the 60s
timeout is too short for cold starts (npm install of 713 packages + gateway
init). Doubles the timeout to 120s and sets the default bundle for OpenClaw
to medium_3_0 (4 GB RAM) since it's too heavy for nano (512 MB).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve openclaw binary path for setsid and add npm-global to Sprite PATH
setsid replaces the process image and doesn't inherit the parent shell's
exported PATH, causing "No such file or directory" on Sprite (and potentially
other clouds). Fix by resolving the full binary path with `command -v` before
passing it to setsid. Also adds ~/.npm-global/bin to Sprite's persisted shell
PATH config so openclaw is discoverable in all session types.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(codex): update wire_api from "chat" to "responses"
Codex CLI dropped support for wire_api = "chat" — it now requires
"responses". This was never updated since the original codex integration,
causing an immediate crash loop on launch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: enable GitHub CLI auth for all agents, not just Claude Code
Only Claude Code had preProvision: promptGithubAuth — all other agents
(codex, openclaw, opencode, kilocode, zeroclaw) skipped GitHub auth
entirely. These are all coding agents that need gh access for PRs,
cloning, etc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add missing spawn import that crashes headless mode (#1981)
runBashHeadless calls spawn() from node:child_process at line 1112,
but only spawnSync was imported. This causes a ReferenceError crash
whenever --headless mode is used.
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
runBashHeadless calls spawn() from node:child_process at line 1112,
but only spawnSync was imported. This causes a ReferenceError crash
whenever --headless mode is used.
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace local rec() helper in hetzner.ts with shared toRecord() from
@openrouter/spawn-shared, eliminating a duplicate implementation that
already existed in the shared package with equivalent behavior
- Fix stale comment in key-request.sh referencing non-existent qa.sh
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>
* feat!: remove Fly.io cloud provider support
Drop Fly.io as a supported cloud provider. Sprite (which uses Fly.io
infrastructure internally) is retained.
- Delete packages/cli/src/fly/ module, sh/fly/ scripts, fixtures/fly/
- Remove fly cloud entry and 6 fly matrix entries from manifest.json
- Remove fly imports, destroy cases, and connection handlers from commands.ts
- Remove fly-ssh sentinel from security.ts
- Port E2E test suite from Fly.io to AWS Lightsail (fly-e2e.sh → aws-e2e.sh)
- Update README (7 clouds, 42 combinations), CLAUDE.md, and skill prompts
- Clean up fly references in build config, gitignore, icon sources
- Bump CLI version to 0.11.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: restore Docker image build under sh/docker/
Move openclaw Dockerfile from sh/fly/docker/ to sh/docker/ and rename
workflow from fly-docker.yml to docker.yml with updated paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: fix extra blank lines in commands.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
The manifest validation (isValidManifest) describe block in
commands-swap-resolve.test.ts used an always-pass anti-pattern:
try { await loadManifest() } catch {} followed by console.error.some()
assertions. This pattern silently passes even when the expected rejection
path is not triggered.
The same coverage (missing agents/clouds/matrix fields, null data, HTTP
errors, valid manifests) is already provided by manifest-cache-lifecycle.test.ts
with proper expect().rejects.toThrow() assertions.
Remove the duplicate 145-line block. No regression: pass/fail counts unchanged.
-- qa/dedup-scanner
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: remove dead offerGithubAuth exports from cloud agents.ts files
The per-cloud offerGithubAuth re-exports in each cloud's agents.ts were
never called from outside their own module. The actual GitHub auth
orchestration is handled by shared/orchestrate.ts which calls
offerGithubAuth from shared/agent-setup.ts directly.
Also update stale comment in sh/test/fixtures/_shared_agent_assertions.sh
that referenced mock.sh, a test harness file that no longer exists in
the repository.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style: collapse multi-line imports to single-line per biome format
After removing offerGithubAuth exports, the remaining 2-import blocks
should be single-line. Also collapse fly/agents.ts 4-import block and
remove trailing blank line.
Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
* test: Remove duplicate and theatrical tests
- Remove aws/agents describe block from aws.test.ts — it duplicated
the identical resolveAgent, agent configs, and generateEnvConfig
tests already present in fly.test.ts; both test the same shared
createAgents/resolveAgent logic from shared/agent-setup.ts
- Remove duplicate dotenv + interactive_prompts checks from
manifest-type-contracts.test.ts "Agent optional field types" section
— these are fully covered by the dedicated "Dotenv configuration"
and "Interactive prompts structure" sections below
- Fix always-skip test in history.test.ts: guard was silently skipping
when running as root (CI environment); replaced with explicit early
return inside block statement
- Fix conditional expects in commands-display.test.ts: the
if (line.includes("cloud")) / if (line.includes("agent")) guards
were unnecessary since every agent/cloud line always contains the
count string; rewrote to unconditional output assertions
- Fix redundant if (resolved) guard in run-path-credential-display.test.ts
after expect(resolved).toBe("claude") already guarantees non-null
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style: fix biome format issues in test files
Remove trailing blank line in aws.test.ts and expand single-line
if-block to multi-line in history.test.ts per biome format rules.
Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
* fix: add ~/.npm-global/bin to OpenClaw PATH for gateway, launch, and reconnect
OpenClaw installs to ~/.npm-global/bin/ via npm, but startGateway() and
launchCmd() only included ~/.bun/bin and ~/.local/bin in PATH — so the
`openclaw` binary was never found on non-Fly clouds (DigitalOcean, Hetzner,
AWS, GCP). Fly was unaffected because it uses setupOpenclawBatched() which
correctly includes the npm-global path.
Three fixes:
1. startGateway(): add $HOME/.npm-global/bin to PATH
2. launchCmd(): add $HOME/.npm-global/bin to PATH
3. install(): persist PATH to ~/.bashrc and ~/.zshrc (matching codex/kilocode
pattern) so reconnects via `spawn openclaw <cloud> --name ...` also work
Closes#1965
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: correct command chaining and idempotency in npm-global PATH setup
- Use curly braces to group grep||echo so PATH append only runs after
successful npm install (fixes operator precedence bug)
- Skip ~/.zshrc modification when file doesn't exist (avoids creating
it on non-zsh systems)
- Use grep -qF for literal string matching (no regex interpretation)
- Apply fix to all three affected agents: openclaw, codex, kilocode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Add Environment Variables section to sh/digitalocean/README.md with:
- DO_REGION: list of all 10 available regions with default (nyc3)
- DO_DROPLET_SIZE: list of all 6 available sizes with default (s-2vcpu-4gb)
- --custom flag: interactive region + size picker
Fixes#1968
Agent: issue-fixer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Two tests used try/catch with assertions in both branches, meaning they
passed whether loadManifest succeeded or threw. The comment claimed local
manifest fallback could be used, but tryLoadLocalManifest() returns null
in test environments (NODE_ENV=test), so the function always throws here.
Replace with expect().rejects.toThrow() which fails if no error is thrown
and eliminates the banned `err: any` type assertion.
Agent: test-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: add npm-global/bin to PATH for openclaw startGateway and launchCmd
Fixes crash where openclaw gateway fails to start on non-Fly clouds
(DigitalOcean, Hetzner, AWS, GCP) because ~/.npm-global/bin was absent
from PATH in startGateway() and launchCmd(). Fly was unaffected because
setupOpenclawBatched() already included the correct PATH.
Fixes#1965
Agent: code-health
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* style: fix Biome format error on launchCmd line
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>