Two fixes for "session forcibly closed" during openclaw install:
1. The openclaw install command piped all output to /dev/null, so
flyctl saw zero bytes flowing and killed the session. Removed
the >/dev/null 2>&1 redirect.
2. Added a background keepalive to runServer that prints a dot to
stderr every 10s. This prevents flyctl from tearing down silent
SSH sessions even if the command itself produces no output for
a while.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bun install -g openclaw spawns child processes that keep stdout/stderr
FDs open, preventing fly ssh console from detecting EOF. Replace with
the official curl installer (--no-onboard) which handles Node detection
and cleanup without leaving orphan processes on the pipe.
See: https://docs.openclaw.ai/install
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs in fly.ts:
1. getCmd() only uses `which` in a subprocess, but Bun.spawnSync inherits
the original PATH — not process.env mutations from ensureFlyCli(). After
installing flyctl to ~/.fly/bin, getCmd() still can't find it. Fix: add
a filesystem fallback that checks ~/.fly/bin directly.
2. ensureFlyToken() resolves the token and saves it to config, but never
writes it to process.env.FLY_API_TOKEN. When fly ssh console runs as a
subprocess, it has no token and can't authenticate. Fix: add
syncTokenToEnv() and call it on every successful token resolution path.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace the Node.js install chain (apt nodejs+npm → npm install -g n →
n 22 → symlinks) with a single curl of the v22 binary tarball from
nodejs.org. Eliminates python3 dependency, npm bloat, and the n version
manager. Bun is installed first as the primary package manager.
Fly-only change — other clouds unchanged pending validation.
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
flyctl tears down the WireGuard transport when stdin closes ("session
forcibly closed; the remote process may still be running"). This
killed long-running commands like `bun install -g openclaw`.
Instead of calling stdin.end() immediately, keep the pipe open for
the duration of the command and close it after the process exits.
The pipe still prevents interactive prompts from hanging (no data
flows through it), but flyctl no longer interprets the closed fd
as a signal to kill the session.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After PR #1602 converted fly/ from bash to TypeScript, fly/lib/common.sh was
removed. However, buildDeleteScript() still generated a bash script that tried
to source it via curl, causing spawn delete to always fail for Fly.io servers
with a curl exit 22 (404). Users were left with orphaned apps incurring charges.
Fix: add fly-specific path in execDeleteServer() that calls ensureFlyCli(),
ensureFlyToken(), and destroyServer() directly from the TypeScript fly module,
bypassing the bash script path entirely. Remove the dead case "fly" from
buildDeleteScript().
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
NodeSource's setup_22.x script adds an APT repo, pulls in python3 as a
dependency, and runs apt-get update twice — slow and heavyweight. Switch
to the same approach used by GCP/Hetzner: install apt's bundled nodejs,
then upgrade to v22 via n with symlinks.
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fly ssh console with stdin as /dev/null ("ignore") can cause the
connection to hang — flyctl doesn't get a clean EOF signal to know
when to close the transport. Switch to "pipe" and immediately call
stdin.end() so flyctl receives a proper EOF.
Applied to runServer, runServerCapture, and uploadFile.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`bun install -g openclaw` spawns child processes that keep stdout/stderr
FDs open, preventing `fly ssh console -C` from detecting EOF and returning.
Wrap in subshell with redirected output so children inherit closed FDs.
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
git on Ubuntu 24.04 pulls in python3 via recommended packages. Use
--no-install-recommends to install only direct dependencies. Also
added ca-certificates explicitly since it's needed for HTTPS but
won't be auto-pulled without recommends.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two fixes:
1. runServer() was inheriting stdin, so commands like `claude install`
that try to read input would hang the terminal indefinitely. Changed
stdin to "ignore" (/dev/null) for non-interactive remote commands.
2. preLaunch failures (e.g. OpenClaw gateway) were silently swallowed,
dropping users into a broken TUI with no gateway. Now preLaunch
errors propagate — users get a clear error instead of a mystery hang.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tzdata (pulled in as a Node.js dependency) tries to run
dpkg-reconfigure interactively, which fails on headless Fly
machines. Set DEBIAN_FRONTEND=noninteractive so apt silently
accepts defaults.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_load_token_from_config intentionally rejects non-ASCII tokens via
regex validation to prevent curl injection. The test expected unicode
tokens to pass, contradicting the code's security design.
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI runs `bun test` from the repo root, not `cli/`, so the
bunfig.toml preload that sets up the sandbox never loads. All 17
tests skip silently — they verify preload infrastructure, not
application code.
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These test files were auto-generated by an AI agent and test copy-pasted
"replica" functions defined inline — not the real source code. They pass
even when the actual code is broken, providing false confidence.
Two categories removed:
1. Replica-only files (34 files, ~1,482 tests): Define inline copies of
functions and test those copies instead of importing from source.
Examples: key-server.test.ts, trigger-server.test.ts,
index-dispatch-routing.test.ts, verb-aliases.test.ts
2. Duplicate-with-imports files (4 files, ~631 tests): Import real
functions but duplicate coverage already in
commands-exported-utils.test.ts. Examples:
commands-credential-display-internals.test.ts (178 tests),
cli-core-edge-cases.test.ts (237 tests)
Before: 131 files, 6,966 tests (5 failing)
After: 93 files, 4,469 tests (1 pre-existing failure)
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Fly Machines API enforces a [1s, 1m0s] range on
WaitMachineRequest.Timeout. We were passing 90s, which caused an
invalid_argument error and prevented machines from starting.
Lower the default to 60s (the API maximum) and retry up to 3 times
so slow-starting machines still have a full 3-minute window.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On CI (GitHub Actions), `CI=true` causes picocolors to enable ANSI
output. Tests comparing against plain text (e.g., `toContain("--prompt
requires a value")`) fail because the actual output wraps text in bold/
dim ANSI codes.
Fixes:
- Subprocess tests (runCli): add NO_COLOR=1 to child env
- Mock capture tests: add stripAnsi() helper to output getters
- Bash subprocess tests: add NO_COLOR=1 to execSync env
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
testFlyToken() fallback to /v1/user accepted 404 plain text responses
because hasError() only checks for JSON "error"/"errors" keys. Adding
resp.ok check ensures non-2xx responses are correctly rejected.
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After the fly provider was converted to TypeScript (PR #1602), the bash
shim scripts no longer source lib/common.sh or reference OPENROUTER_API_KEY
directly -- that logic moved to TypeScript. Skip TypeScript shim scripts
in bash-specific convention checks.
Also fixes:
- URL regex in cloud-error-guidance to exclude backticks/commas from
template literals in heredocs
- aws added to skipProviders for destroy_server error check (uses set -e
and internal process.exit, not explicit return 1)
- inject_env_vars_local test regex updated to match semicolon separator
instead of && (matches actual shared/common.sh implementation)
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Move all fly TypeScript files from fly/lib/*.ts and fly/main.ts into
cli/src/fly/. This gives them access to cli/node_modules (@clack/prompts),
biome linting, and the existing bun:test infrastructure — no symlinks or
NODE_PATH hacks needed.
The org picker now uses @clack/prompts select() directly (static import,
bundled at build time).
New: cli/build-clouds.sh — auto-discovers cli/src/*/main.ts and bundles
each into {cloud}.js. Scalable to future cloud TS migrations:
bash cli/build-clouds.sh # build all
bash cli/build-clouds.sh fly # build one
Shims now check for cli/src/fly/main.ts (local) or download fly.js from
GitHub releases (remote curl|bash).
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The only existing installer (install.sh) is bash-only and fails silently
on Windows PowerShell — 'curl ... | bash' errors because bash.exe is not
available outside WSL.
install.ps1 implements the same logic as install.sh for PowerShell:
- Checks bun >= 1.2.0; installs via bun.sh/install.ps1 if missing
- Downloads CLI source via git sparse-checkout or GitHub API fallback
- Builds with 'bun install && bun run build'; falls back to pre-built binary
- Installs to %USERPROFILE%\.local\bin (or SPAWN_INSTALL_DIR override)
- Creates spawn.cmd wrapper for cmd.exe compatibility
- Adds install dir to the user's persistent PATH if not already present
Usage:
irm https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cli/install.ps1 | iex
README updated with Windows PowerShell install instructions alongside
the existing macOS/Linux/WSL command.
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The aws destroy_server function had conditional logic (if/else for CLI
vs REST mode) but no error handling - failures were silently ignored and
"Instance destroyed" was logged even on failure. This could leave
instances running and incurring charges without the user knowing.
Also fix the URL extraction regex in cloud-error-guidance.test.ts to
exclude backtick characters, preventing false positives from template
literals in embedded TypeScript code.
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
promptSpawnName() used `placeholder` (visual hint only) without `defaultValue`,
so pressing Enter returned an empty string instead of applying the placeholder.
Now generates a unique default like `spawn-a3f2` with a random suffix to avoid
Fly.io global name collisions.
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fly auth token deprecated + org picker + macaroon discharge tokens
Three fixes for the fly/ TypeScript provider:
1. `fly auth token` is deprecated — newer flyctl outputs a message, not
a token. Now tries `fly tokens create org --expiry 24h` first, with
`fly auth token` as fallback. Uses org tokens (not deploy) since
spawn needs to create new apps.
2. Token sanitization stripped macaroon discharge tokens at commas
(`fm2_[^ ,]*` → `fm2_\S+`). The full composite token
`fm2_xxx,fm2_yyy,fo1_zzz` is now preserved.
3. Org picker upgraded from numbered 1/2 input to arrow-key interactive
selector with cursor navigation, scroll windowing, and fallback to
numbered list when TTY is unavailable.
Also fixes: testFlyToken fallback sent `Bearer FlyV1 ...` (double prefix)
for macaroon tokens — now dispatches FlyV1 vs Bearer correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: never run test/mock.sh locally — opens browser, CI only
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace fly/lib/common.sh (741 lines of bash) with a TypeScript
implementation using Bun runtime. The fly/ provider was the most
complex bash code in the project — recent fixes (#1597, #1599, #1600)
highlight the pain of debugging HTTP calls, JSON parsing, and multi-step
auth flows in shell.
New TypeScript modules:
- fly/lib/ui.ts — logging, prompts, validation (zero deps)
- fly/lib/fly.ts — API client (fetch), auth chain, org listing, provisioning
- fly/lib/oauth.ts — OpenRouter OAuth via Bun.serve(), key management
- fly/lib/agents.ts — typed agent configs for all 6 agents
- fly/main.ts — orchestrator entry point
Agent .sh files become thin shims (~30 lines) that install bun if needed,
download TS sources for curl|bash execution, and delegate to main.ts.
Test coverage:
- 44 TypeScript unit tests (bun test) for pure logic
- 4 fly failure-mode tests (mock.sh) for error scenarios
- All existing test suites pass (110 run.sh, 76 mock.sh)
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: validate token characters in _load_token_from_config to prevent curl injection
Tokens loaded from ~/.config/spawn/{cloud}.json were exported without
character validation. A tampered config file containing a token with
embedded newlines could exploit the _curl_api function's -K - (stdin
config) mechanism to inject arbitrary curl directives (e.g., output,
url), since curl interprets newlines in the config format as directive
separators.
Add allowlist validation (^[a-zA-Z0-9._/@:-]+$) matching the pattern
already used in key-request.sh _try_load_env_var and validate_api_token,
making all three token-loading paths consistent.
Agent: security-auditor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address review feedback on token validation PR
- Update backslash test to expect validation failure (backslashes not
valid in any known API token format; the old expectation was wrong
after validation was added)
- Fix test so exit code comes from _load_token_from_config directly,
not the trailing echo which always exits 0
- Add comment in shared/common.sh explaining why the pattern includes
colon vs key-request.sh pattern (Fly.io FlyV1 tokens use colons)
Agent: pr-maintainer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address review feedback — widen token charset for base64 segments
The original regex rejected + and = which are valid base64 characters
found in API tokens (e.g. sk-or-v1-abc/def+ghi==). This caused a
pre-existing test to fail. Widen the allowlist to include + and =
while keeping the security comment documenting the pattern difference
with key-request.sh.
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.6 <noreply@anthropic.com>
The test-infra-sync test validates that mock.sh's _strip_api_base() and
_validate_body() cover all clouds with fixtures. However, the actual
runtime mock used by tests is mock-curl-script.sh, which has its own
copies of these functions. Nothing enforced these copies staying in sync,
so a contributor could update mock.sh to pass validation while the
runtime mock silently fails to handle new cloud URLs.
Add cross-file sync tests that verify both files handle the same cloud
patterns for _strip_api_base() and _validate_body(). Also refactor
helpers to accept content as a parameter for reuse across both files.
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Add autocomplete mock to 38 @clack/prompts mock.module declarations
that were missing it. Bun's mock.module is process-global, so when any
other test file's mock wins the race, p.autocomplete was undefined,
causing 17 cmd-interactive tests to fail non-deterministically.
Also guard sandbox-verification tests with describe.skipIf(!isSandboxed)
so the 8 meta-tests skip cleanly when running from repo root (where
bunfig.toml preload is not active) instead of failing.
Result: 6995 pass, 0 fail from cli/; 6978 pass, 0 fail, 17 skip from root.
Agent: test-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The sprite case in buildDeleteScript called `sprite destroy` directly,
bypassing ensure_sprite_authenticated and destroy_server. This meant
SPRITE_ORG was never detected, so org users got "sprite not found"
errors and orphaned sprites continued incurring charges.
Align with every other cloud (hetzner, digitalocean, fly, gcp, aws,
daytona) by calling ensure_sprite_authenticated then destroy_server,
which applies _sprite_org_flags automatically.
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
spawn delete was broken for all clouds because execDeleteServer passed
inline scripts (without shebangs) through runBash, which calls
validateScriptContent requiring a #! prefix. Extract spawnBash helper
and add runBashTrusted for locally-generated delete scripts that already
validate their inputs via validateServerIdentifier/validateMetadataValue.
Also fix instanceof Error usage in manifest.ts and history.ts to use
duck typing, matching the convention documented in index.ts and
commands.ts. Fix stale comment in security.ts that claimed colons were
in the server ID allowlist when the regex excludes them.
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Tests fell out of sync with recent source changes:
- _display_and_select: check for "server types" (agnostic of UI path)
- opencode_install_cmd: check for "tr A-Z a-z" (new OS detection)
- _curl_api: test non-auth headers (auth now via -K stdin)
- ensure_gh_auth: use valid token prefix, match new log messages
- GITHUB_TOKEN piping: match _gh_token variable name
- daytona: remove from exec-based clouds (uses SSH)
- cmdrun/prompt-file: add --dry-run to prevent script execution timeouts
- sandbox: clean stale /root/subprocess-test.txt before assertion
Agent: test-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- update-check.test.ts: mock execFileSync for re-exec path added in eea43ad,
account for findUpdatedBinary() "which spawn" call, update bare-spawn test
to expect re-exec instead of "Run your spawn command again"
- upload-file-security.test.ts: fix sprite classification to match
"sprite $(...) exec" with org flags; remove daytona from strict allowlist
regression list (uses printf %q escaping, validated by general exec tests)
- version-comparison.test.ts: mock execFileSync for auto-update integration test
Agent: test-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
validateConnectionIP rejected "localhost" (written by local cloud) and
hostnames like "ssh.app.daytona.io" (written by Daytona), causing
mergeLastConnection to silently discard connection data. This broke
spawn list and spawn delete for these providers.
- Add "localhost" to CONNECTION_SENTINELS
- Add HOSTNAME_PATTERN for valid multi-label DNS hostnames
- Update tests: localhost now valid, add hostname acceptance/rejection tests
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Two bugs in reExecWithArgs():
1. args.length === 0 early exit:
Running bare `spawn` (interactive picker) after an auto-update would
print "Run your spawn command again" and exit, requiring the user to
manually re-invoke. Now always re-exec so the new flow triggers
immediately.
2. process.argv[1] stale binary path:
If the installer places the updated binary in a different directory than
the currently running binary (e.g. old: ~/.local/bin, new: /usr/local/bin),
re-exec would run the old stale binary. Fix: add findUpdatedBinary() which
resolves via `which spawn` (PATH lookup) first, falling back to
process.argv[1] only if which fails.
Bump CLI version 0.5.17 → 0.5.18.
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Users who name their spawns via the interactive "Name your spawn" prompt
cannot see those names in `spawn list` output. Multiple spawns of the
same agent/cloud combo (e.g. two "Claude Code on Hetzner") are
indistinguishable despite having different names.
Show the spawn name in both interactive picker labels and non-interactive
table output so users can tell their spawns apart.
Agent: ux-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove nonexistent `ensure_daytona_cli` call from Daytona delete script
(causes "command not found" error when running `spawn delete` on Daytona)
- Add Fly.io SSH handler in cmdConnect to use `fly ssh console -a NAME`
instead of falling through to broken `ssh root@fly-ssh` path
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: replace hardcoded ~/.spawn/history.json path in security.ts error messages
Error messages in security validation functions (validateConnectionIP,
validateUsername, validateServerIdentifier, validateMetadataValue) hardcoded
~/.spawn/history.json as the fix path. This is wrong when SPAWN_HOME is set,
directing users to a nonexistent file. Replace all 9 occurrences with
'spawn list --clear' which works regardless of SPAWN_HOME and is simpler
than manually editing JSON.
Agent: ux-engineer
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: bump cli version to 0.5.17
Required by CLAUDE.md: any change to cli/ needs a version bump.
PR #1520 changes security.ts error messages (cli/ change).
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>
The test's runCli() helper used \${process.env.HOME}/.bun/bin/bun as
the subprocess command. The test preload sandboxes HOME to a temp dir,
so this path resolves to a nonexistent file, causing ENOENT and 49/56
test failures.
Fix: use bare "bun" (resolved via PATH), matching the pattern in
cli-version-and-dispatch.test.ts and cmdrun-resolution.test.ts.
All 56 tests in cli-entry-edge-cases.test.ts now pass.
Agent: team-lead
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Change "Enter a name for this spawn (optional)" to "Name your spawn"
and remove the restrictive alphanumeric-only validation. Display names
can now include spaces, uppercase, and special characters (e.g.
"My Claude Box"). The shell scripts derive a kebab-case slug for the
actual cloud resource name via _to_kebab_case() in shared/common.sh.
Bump CLI version 0.5.14 → 0.5.15.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two error messages told users to run 'spawn clear-history' when
encountering corrupted history files, but that command does not exist.
The actual command is 'spawn list --clear'. Users got a confusing
"Unknown agent or cloud: clear-history" error when following the advice.
Agent: ux-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Switch runCli helper from execSync to spawnSync so stderr is always
captured (execSync only returns stderr on non-zero exits, causing
extra-arg warning tests to fail). Add --dry-run to tests that pass
valid agent+cloud combos to avoid triggering actual script execution
and timing out under bun's 5s per-test limit.
Agent: test-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Enrich each agent entry with curated metadata fields: creator, repo,
license, created/added dates, GitHub stars, language, runtime, category,
tagline, and tags. This helps users compare and choose agents.
- Extend AgentDef interface with 12 optional metadata fields
- Add metadata to all 6 agents in manifest.json
- Add type validation tests for new fields
- Bump CLI version 0.5.12 → 0.5.13
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Why: `set -eo pipefail` + `output=$(shellcheck ...)` on line 659 of
test/run.sh causes immediate exit when shellcheck finds any warning,
preventing the entire shell test suite from running. 53 CLI tests also
fail due to stale assertions after agents/clouds were removed in recent
PRs.
Fixes:
- test/run.sh:659 — add `|| true` to shellcheck command substitution so
shell test suite runs to completion even when scripts have warnings
- manifest-real-data.test.ts — lower agent count min from 10→5,
matrix count min from 80→40 (now 6 agents, 48 matrix entries)
- agent-env-injection-contract.test.ts — lower script count min
from 70→40 (now 47 implemented scripts)
- script-conventions.test.ts — same script count fix (70→40)
- cloud-lib-source-chain.test.ts — lower cloud lib min from 9→8
(OVH removed, now 8 clouds)
- commands-credential-display-internals.test.ts — add missing
@clack/prompts mock (tests call p.log.error but never mocked it)
- commands-exported-helpers-edges.test.ts — fix environment-dependent
assertion: only check credential-based hintOverrides, not
CLI-installed ones (sprite CLI is installed in CI/dev)
- agent-config-setup.test.ts — fix stale model ID assertion
("openrouter/anthropic/..." → "anthropic/...") and stale mkdir
command ("rm -rf && mkdir" → "mkdir -p")
- agent-info-quickstart.test.ts — remove sprite from singleAuthManifest
fixture (sprite CLI installed causes sprite to be prioritized over
hetzner, breaking 4 tests); update count assertions for single cloud
Agent: team-lead
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Point OpenClaw to https://github.com/openclaw/openclaw and OpenCode to
https://github.com/anomalyco/opencode. Update the OpenCode install command
and binary download URL to match the new repo.
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>