Commit graph

1459 commits

Author SHA1 Message Date
A
3e5cd2d076
fix: spawn fails with bun not found after install (#1748)
* fix: add ~/.bun/bin to shell rc files so spawn finds bun after install

The install script was only adding ~/.local/bin to shell profile files
(bashrc/zshrc/bash_profile), but not ~/.bun/bin. Since the spawn binary
uses #!/usr/bin/env bun as its shebang, bun must be in PATH for spawn
to work. After exec $SHELL, only dirs in rc files are available.

Now ensure_in_path() patches shell rc files for both ~/.local/bin (for
spawn) and ~/.bun/bin (for bun), and correctly checks both when deciding
whether to show "Run spawn" vs "exec $SHELL" instructions.

Fixes #1747

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

* fix: quote dir in fish_add_path to prevent command injection

Address security review feedback on PR #1748 — unquoted ${dir} in
fish command string could allow injection if HOME/BUN_INSTALL env
vars contain metacharacters.

Agent: code-health
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-02-22 13:41:27 -08:00
A
57d4ee7eeb
fix: drop apt nodejs/npm, install Node 22 directly via n (#1746)
apt-get install nodejs npm pulls in hundreds of node-* packages
(libhwasan, node-jsonify, node-eslint-utils, etc.) adding 60-90s
to cloud-init. We immediately replace it with Node 22 via n anyway.

Fix: bootstrap n directly from curl and install Node 22 in one step.
No apt nodejs/npm needed.

Before: apt install nodejs npm → npm install -g n → n 22 (slow)
After:  curl n | bash -s install 22 (fast, no apt bloat)

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 12:40:22 -08:00
A
0a8e6a30ad
fix: symlink bun alongside spawn in /usr/local/bin during install (#1745)
spawn uses #!/usr/bin/env bun shebang, so bun must be in PATH for it
to run. When ~/.local/bin isn't in PATH and we symlink spawn into
/usr/local/bin, bun (typically at ~/.bun/bin/bun) isn't reachable,
causing "bun: not found" on first run. Now symlink bun too.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 12:36:52 -08:00
A
9d3728fd8d
fix: add build-essential to node cloud-init tier (#1743)
* fix: add build-essential to node cloud-init tier

The "node" tier (used by claude, codex, kilocode) was missing
build-essential. Native npm packages that compile C/C++ addons
fail without it. The "full" tier had it but no agent uses "full".

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

* fix: upgrade openclaw to full cloud-init tier

Openclaw needs the most dependencies (build-essential, nodejs, npm,
bun) but was on the "bun" tier which only installed curl/unzip/git/zsh.
Switch to "full" which includes everything.

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>
2026-02-22 12:27:11 -08:00
A
fd7c878e83
fix: replace broken buildDeleteScript with TypeScript cloud module calls (#1735)
`spawn delete` was broken for all non-Fly clouds (hetzner, digitalocean,
gcp, aws, daytona, sprite) because buildDeleteScript sourced
{cloud}/lib/common.sh files that were removed in the TypeScript rewrite.

Fix by removing buildDeleteScript entirely and rewriting execDeleteServer
to call each cloud's TypeScript destroyServer/destroyInstance directly,
following the same pattern already used for Fly.io. All clouds now use
their native TS module for auth + deletion with no shell dependency.

Fixes #1729

Agent: issue-fixer

Signed-off-by: L <6723574+louisgv@users.noreply.github.com>
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 12:22:08 -08:00
A
5aa8a50a94
fix: skip symlink when ~/.local/bin already in PATH, use Touch ID (#1742)
* fix: skip /usr/local/bin symlink when ~/.local/bin is already in PATH

Only symlink when actually needed, and never prompt for a password:
- If ~/.local/bin is in PATH, skip symlink (not needed)
- If not, try direct write to /usr/local/bin first
- Fall back to sudo only if passwordless (NOPASSWD, cached creds,
  or macOS Touch ID via pam_tid)
- Otherwise patch rc files and show `exec $SHELL`

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

* fix: prompt for password as last resort when symlinking spawn

Symlink priority: direct write → passwordless sudo (NOPASSWD/Touch ID)
→ prompt for password → fall back to exec $SHELL.

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

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 12:21:07 -08:00
A
bba0e9a7b4
fix: make spawn immediately available after install (#1738)
* fix: make spawn immediately available after install on fresh machines

The install script modified PATH internally to find bun, then checked
that same modified PATH to decide where to install and whether to warn.
On a fresh machine, ~/.local/bin wasn't in the user's real PATH, but
the script thought it was — so spawn was installed there with no
warning, and `spawn` was not found after install.

Fix: always install to ~/.local/bin, then symlink into /usr/local/bin
(which is universally in PATH). Also patch shell rc files for future
sessions. If symlinking fails, fall back to showing `exec $SHELL`.

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

* test: update install.sh tests for new symlink-based install

Remove find_install_dir tests (function removed), update ensure_in_path
tests for symlink + rc-patching behavior, fix section header reference.

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

* fix: replace [[ ]] with case for macOS bash 3.x compatibility

Addresses security review: [[ ... == */pattern ]] is bash 4.x syntax
that fails on macOS's default bash 3.2. Use POSIX case statements.

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

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 12:14:34 -08:00
A
30f3758902
fix: set HOME=/root in cloud-init userdata to prevent unbound variable (#1741)
DigitalOcean's cloud-init environment doesn't set HOME. Combined
with set -e, any $HOME or ~ reference (bun install, .bashrc writes)
fails with "HOME: unbound variable" and cloud-init silently aborts.

Fixed in both DigitalOcean and Hetzner (same pattern). AWS doesn't
use set -e so is unaffected.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 12:13:56 -08:00
A
dc21fa223b
fix: cloud-init streaming script bash syntax error (#1737)
.join("; ") produced invalid bash: &; after background command,
do; after for, then; after if. Use newline-joined string instead.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 12:05:54 -08:00
A
b50b27141c
fix: stream cloud-init output instead of blind-polling on DigitalOcean (#1734)
Replace 60×5s blind poll loop ("Cloud-init in progress N/60") with
real-time streaming of /var/log/cloud-init-output.log via tail -f
over SSH. Users now see every apt-get, curl, and error as it happens.

Background checker exits as soon as .cloud-init-complete marker
appears. 5min timeout. Brief 30s fallback poll if streaming fails.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:58:29 -08:00
A
ac5e8495b1
feat: customize cloud-init per agent to fix boot timeouts (#1733)
Agents declare their dependency tier (minimal/node/bun/full), and
cloud-init only installs what's needed. Lightweight agents like
OpenCode and ZeroClaw skip Node.js upgrade, Bun install, and
build-essential — saving 60-90s on boot and eliminating the
DigitalOcean cloud-init timeout.

- Add CloudInitTier type + cloudInitTier field to AgentConfig
- Add shared/cloud-init.ts: tier-to-packages mapping
- Update all 6 clouds (DO, Hetzner, AWS, GCP, Fly, Daytona)
- Bump CLI version to 0.6.8

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:43:45 -08:00
A
d653f282ed
fix: prevent refactor team from closing PRs (#1732)
* fix: prevent refactor team from closing PRs — security team only

The refactor team-lead incorrectly closed human-created PR #1728,
claiming it touched off-limits files. Multiple fixes:

- Remove pr-maintainer's ability to close PRs via "comment-based triage"
- Add explicit rule: NEVER close or modify human-created PRs
- Reinforce in 5 places: only security team can close PRs
- Only exception: superseding with a replacement PR (must cite number)
- Add guard: only interact with PRs containing `-- refactor/` signature

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

* fix: add global comment dedup rule for all refactor teammates

Before posting ANY comment on a PR or issue, every teammate must
check for existing `-- refactor/` signatures to avoid duplicate
comments with the same intent.

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>
2026-02-22 11:43:00 -08:00
A
5524559a9d
fix: remove stale test/shell refs from prompts, add issue-filing rule (#1731)
- CLAUDE.md: remove Mock Test Infrastructure section, update testing
  docs to unit-test-only, fix file structure, add "Filing Issues for
  Discovered Problems" rule
- discovery-team-prompt.md: test/record.sh + test/mock.sh → bun test
- security-review-all-prompt.md: remove "verify source fallback pattern"

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:37:42 -08:00
A
01ba7257ed
fix: don't re-prompt name for failed spawns, improve retry hint (#1719)
* fix: don't re-prompt name for failed spawns, improve retry hint

- Reuse existing spawn name when rerunning from `spawn list` or
  `spawn last` instead of prompting for a new name (#1712)
- Include --name flag in retry command hint when a spawn name
  was used, e.g. `spawn claude hetzner --name my-box` (#1709)
- Bump CLI version to 0.6.5

Fixes #1712
Fixes #1709

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

* test: add unit tests for buildRetryCommand --name flag

Cover the spawnName parameter added for issue #1709:
- with name and no prompt
- with name and short prompt
- with name and long prompt (prompt-file fallback)
- with undefined/empty name (no --name flag)
- verify --name appears before --prompt in output

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

* fix: quote --name value when spawn name contains spaces

Handle edge case where spawn names may contain spaces or quotes
by properly quoting and escaping the --name flag value.

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

* fix: always quote --name value to prevent shell injection

Always wrap spawn names in double quotes in the retry command hint,
not just when names contain spaces. This prevents shell metacharacters
(;, |, &, etc.) in spawn names from being interpreted if users copy
the displayed retry command.

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-02-22 11:36:16 -08:00
A
60986e5a05
refactor: remove shared/common.sh and 27 subprocess-heavy test files (#1728)
shared/common.sh (3852 lines) was dead code — the entire architecture
was rewritten to TypeScript in cli/src/. No agent scripts source it
anymore. The only consumer was github-auth.sh which just needed 4
log functions (now inlined).

Remove 27 test files that spawned ~800+ real bash/bun subprocesses per
run (the root cause of slow bun test). Every shared-common-*.test.ts
file forked a real bash shell per test case to source shared/common.sh.
CLI subprocess tests spawned `bun run index.ts` per assertion. These
were integration tests, not unit tests.

Also removes:
- mock-tests CI job from test.yml (ran test/mock.sh which opens browser)
- Stale plan files referencing deleted infrastructure
- All CLAUDE.md/README.md references to the old lib/common.sh pattern

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:32:27 -08:00
A
738ad18fee
fix: add 5s delay between DigitalOcean and OpenRouter OAuth flows (#1727)
When both OAuth flows open browser tabs back-to-back, the user may
reactively close the second tab thinking it's a duplicate. Add a 5-second
pause with a message after DO OAuth completes, only when browser auth
was actually used (skipped for env var / saved token paths).

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:27:14 -08:00
A
5192912b68
fix: auto-update silently fails in multiple scenarios (#1725)
- Prevent recursive update check during install (SPAWN_NO_UPDATE_CHECK=1)
- Increase fetch timeout from 5s to 10s for slow/cold connections
- Add 1-hour failure backoff to avoid repeated failed update attempts
- Bump CLI version to 0.6.6

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:17:54 -08:00
A
f2010ce3bd
fix: add account:read scope to DigitalOcean OAuth flow (#1724)
OAuth token validation calls GET /v2/account which requires the
account:read scope. Without it, the token exchange succeeds but
validation fails with 403, falling through to manual token entry.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:14:26 -08:00
A
be72d573e1
fix: resolve DigitalOcean token input validation bug (#1720)
Reuse a single readline interface across prompt() calls instead of
creating and closing a new one each time. In Bun, repeatedly calling
createInterface/close on the same stdin causes the "close" event to
fire immediately on subsequent interfaces, which resolved the prompt
with an empty string before the user could type — triggering "Token
cannot be empty".

Fixes #1707

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 11:08:01 -08:00
A
3204ce8eec
refactor: rewrite QA service as thin bash harness + .md prompts (#1719) (#1721)
Replace the 1,190-line qa-cycle.sh monolith with the security.sh
pattern: thin bash launcher (qa.sh) + separate .md prompt files for
each run mode.

Three modes:
- quality (schedule/manual): agent team with test-runner, dedup-scanner,
  and code-quality-reviewer
- fixtures (reason=fixtures): single-agent API fixture collector
- issue (reason=issues): single-agent issue investigator/fixer

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:07:31 -08:00
A
33bd3e615c
chore: disable QA workflow schedule until VM is fixed (#1722)
Keep workflow_dispatch for manual testing. Re-enable cron when the
QA VM is back online.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 11:06:50 -08:00
A
e527d79815
feat: DigitalOcean OAuth2 flow for automatic token provisioning (#1716)
* feat: add DigitalOcean OAuth2 flow for automatic token provisioning

Implements the OAuth2 authorization code flow for DigitalOcean as an
alternative to manual API token entry. The flow mirrors the existing
OpenRouter OAuth pattern using Bun.serve() for the local callback.

Changes:
- Add tryDoOAuth() with local Bun.serve callback, CSRF state, and
  code-for-token exchange via DO's /v1/oauth/token endpoint
- Add tryRefreshDoToken() for refreshing expired tokens without
  re-authorization
- Extend config persistence with refresh_token, expires_at, auth_method
- Modify ensureDoToken() flow: env var -> saved config (with refresh) ->
  OAuth browser flow -> manual paste fallback
- OAuth is gated on DO_OAUTH_CLIENT_ID and DO_OAUTH_CLIENT_SECRET env vars
- Add 37 tests covering config persistence, CSRF generation, code
  validation, token expiry, URL construction, and feature toggle
- Bump CLI version to 0.6.5

Closes #1715

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

* feat: hardcode DO OAuth credentials, remove env var gate

Embed client_id and client_secret as constants (same pattern as gh CLI,
doctl, gcloud). OAuth is now always available — no env vars needed.
Public CLI clients cannot keep secrets confidential; security comes from
the authorization code flow itself (user consent, localhost redirect,
CSRF state, single-use codes).

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

* feat: add droplet:delete scope for spawn delete support

The spawn CLI's destroyServer() calls DELETE /droplets/{id} which
requires the droplet:delete scope. All its required sub-scopes
(droplet:read, regions:read, sizes:read, actions:read, image:read)
were already present.

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

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 13:49:53 -05:00
A
32a54d317b
fix: re-apply npm prefix for kilocode/codex in shared agent-setup (#1713)
The npm-global prefix fix from PR #1699 was lost when agent configs
were refactored from gcp/agents.ts into shared/agent-setup.ts. Without
`npm config set prefix ~/.npm-global`, npm install -g uses the system
prefix (/usr/lib/node_modules) which fails with EACCES for non-root
users on GCP.

This also fixes kilocode's postinstall script (postinstall.mjs) which
uses require.resolve() to find @kilocode/cli-linux-x64 — when npm
writes to the system prefix, the postinstall can't write the binary
symlink into the package's bin/ directory.

Fixes #1698

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 13:49:51 -05:00
L
e3cebbbb49
test: remove Python-using bash tests and security theater (#1718)
Deleted 8 test files (5,581 lines) that violated CLAUDE.md policies:

Security Theater (981 lines):
- test/test-sandbox.sh - tested test infrastructure pollution
- test/qa-dry-run.sh - meta-testing nightmare (672 lines)
- test/update-readme.py - Python script

Python-Using Bash Tests (4,600 lines):
- test/e2e.sh - 11 Python calls for JSON parsing
- test/run.sh - 6 Python calls
- test/mock.sh - 6 Python calls
- test/record.sh - 14 Python calls
- test/mock-curl-script.sh - 2 Python calls

Per CLAUDE.md: "Use Bun + TypeScript for inline scripting — NEVER python"

Changes:
- Removed all Python usage from bash test suite
- Fixed .claude/skills/setup-agent-team/qa-cycle.sh (disabled update-readme.py)
- Fixed cli/src/__tests__/cloud-error-guidance.test.ts (updated expectation)
- Documented rationale in .docs/REMOVED_TESTS.md
- Test coverage: 82 TypeScript test files, 3,570/3,571 passing (99.97%)

Remaining bash tests:
- test/macos-compat.sh (no Python, pure bash linter)

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 10:45:01 -08:00
A
ad076688e9
refactor: deduplicate bun test suite, remove 5.3k redundant lines (#1717)
Delete 5 entirely-duplicate test files and trim 9 others where the same
bash functions were tested identically in multiple places. Every removed
test has a surviving canonical copy — zero coverage lost.

Deleted (all content duplicated elsewhere):
- shared-common-decomposed-helpers.test.ts
- shared-common-oauth-retry.test.ts
- shared-common-oauth-security.test.ts
- shared-common-server-retry.test.ts
- shared-common-token-provider.test.ts

79 files / 38k lines → 74 files / 33k lines

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 10:30:38 -08:00
A
9cb265d820
refactor: remove all cloud bash libs, convert AWS to JS bundle fallback (#1714)
All clouds now use TypeScript. Convert the last holdout (AWS) from bash
lib fallback to the JS bundle download pattern, then delete all remaining
cloud bash libs and clean up stale test code.

- Convert 6 AWS agent scripts to JS bundle fallback (matching hetzner)
- Delete aws/lib/common.sh and hetzner/lib/common.sh
- Delete orphaned test/fixtures/ovh/
- Stub out dead functions in test/e2e.sh that sourced deleted libs
- Delete 3 test files that only tested cloud bash libs
- Remove dead describe blocks from 3 remaining test files
- Bump CLI version 0.6.3 → 0.6.4

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 10:13:36 -08:00
A
2303e65022
fix: allow rich text in bug report issue template (#1710)
Remove `render: shell` from the "What happened?" textarea so users can
paste screenshots, drag & drop files, and use markdown formatting
instead of being forced into a plain-text code block.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 10:04:20 -08:00
A
fdd721bf09
refactor: extract agent team prompts into .md files (#1708)
Move all inline heredoc prompts out of orchestrator scripts into
standalone .md files. Rename discovery-team-prompt.txt to .md.
Orchestrators now read from the .md templates and sed-substitute
placeholders at runtime — same behavior, cleaner separation.

- discovery: .txt → .md rename + reference update
- refactor: 2 prompts (issue + team) extracted to .md
- security: 4 prompts (team-building, triage, review-all, scan) extracted to .md

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 10:03:11 -08:00
A
e28deca91b
fix: replace python3 with bun/jq in shared scripts (#1697) (#1701)
* fix: replace python3 with bun/jq in shared scripts (#1697)

Replace python3 -c inline scripting with jq (preferred) and bun -e
fallbacks per project policy. Python is not a declared dependency;
jq and bun are the project's scripting runtimes.

Changes:
- shared/common.sh: Replace all 9 python3 -c calls with jq/bun -e
- shared/key-request.sh: Replace all 4 python3 -c calls with jq/bun -e
- check_python_available: Now checks for jq or bun instead of python3
- Update test expectations for JS semantics (true/false vs True/False,
  bracket access vs .get(), null handling)

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

* fix: replace eval() with safe property access, rename check_python_available

Security: eliminate eval() from _extract_json_field() — use regex-based
bracket-notation parser to traverse JSON paths safely. The function now
extracts ['key'] and [N] segments from the expression string and
iterates through them, preventing arbitrary code execution.

Also rename check_python_available() → check_json_processor_available()
throughout the codebase (shared/common.sh, local/lib/common.sh, and
tests) since the function now checks for jq/bun, not python3.

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

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 09:57:49 -08:00
A
945b60317c
fix: clean up stray subprocess-test-*.txt files in preload (#1703)
Automated refactor/discovery agents occasionally run tests from outside
the cli/ directory, where bunfig.toml is not loaded and this preload
never activates.  When that happens, HOME stays as the real home dir
(/root on CI), so any subprocess-test-*.txt written by tests leaks
there instead of the sandbox.

Added cleanupStrayTestFiles() which runs both on preload init and on
process exit.  This retroactively removes any leftover files from past
runs and prevents accumulation in future ones.

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 09:29:22 -08:00
A
c1b42d3f97
refactor: remove dead local/lib/common.sh (#1705)
No scripts reference this file since local was converted to TypeScript.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 09:28:45 -08:00
A
d79c941593
feat: draft PR workflow + stale PR cleanup for agent teams (#1706)
Agents now open draft PRs immediately after first commit and convert
to non-draft when work is complete, enabling visibility into in-progress
work. Security reviewer skips draft PRs. Stale drafts (7+ days) are
auto-closed. Refactor pr-maintainer picks up stale non-draft PRs (3+ days).

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 09:28:12 -08:00
A
21f7e7683f
refactor: deduplicate remaining 6 clouds into shared agent-setup pattern (#1704)
Convert gcp, daytona, digitalocean, hetzner, sprite, and local clouds
to use shared/agent-setup.ts and shared/orchestrate.ts, matching the
pattern established by AWS and Fly. Each cloud's agents.ts is now a
~26-line thin wrapper; each main.ts uses runOrchestration().

- Delete gcp/lib/common.sh (406 lines of dead bash code)
- Delete cli/src/fly/oauth.ts and cli/src/fly/ui.ts re-export wrappers
- Fix all fly/oauth and fly/ui imports to use shared/ directly
- Update test thresholds for reduced bash cloud count
- Bump CLI version to 0.6.3

Net reduction: ~2,850 lines removed.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 09:20:34 -08:00
A
502150072a
fix: resolve permission errors installing kilocode on gcp (#1698) (#1699)
GCP VMs run as a non-root user, so `npm install -g` fails when the npm
prefix points to a system directory. Ensure ~/.npm-global is configured
as the npm prefix before global installs for kilocode, codex, and
openclaw (npm fallback).

Fixes #1698
Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 11:49:38 -05:00
L
bd75880648
fix: add test directory cleanup to security.sh (#1702)
- Clean up spawn-cmdlist-test-* directories before each cycle
- Clean up spawn-cmdlist-test-* directories after each cycle in cleanup trap
- Prevents accumulation of CLI integration test directories in /root
2026-02-22 08:41:52 -08:00
A
eac5713ef0
refactor: deduplicate AWS/Fly agent setup into shared modules (#1700)
Extract ~800 lines of duplicated agent helpers and orchestration logic
from aws/agents.ts and fly/agents.ts into shared modules:

- shared/agent-setup.ts: CloudRunner interface, installAgent,
  uploadConfigFile, installClaudeCode, setupClaudeCodeConfig,
  GitHub auth, config helpers, createAgents(), resolveAgent()
- shared/orchestrate.ts: CloudOrchestrator interface + 12-step
  runOrchestration() pipeline
- shared/agents.ts: AgentConfig type + generateEnvConfig (single source)

Each cloud becomes a thin wrapper (~25-60 lines) that constructs a
CloudRunner/CloudOrchestrator from its provider-specific functions.

Also fixes pre-existing test breakage (aws.test.ts imported renamed
exports LIGHTSAIL_BUNDLES/BundleTier → BUNDLES/Bundle) and removes
dead aws/lib/common.sh reference from test/e2e.sh.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 08:40:21 -08:00
A
0bdec8e890
fix: update test_source_detection for bun shim pattern (#1696)
test_source_detection was checking sprite/*.sh for the old
lib/common.sh sourcing pattern which was removed when all cloud
providers were converted to TypeScript bun shims (PRs #1691-1694).
This caused 6 test failures on every run.

Updated to check all 8 cloud providers for the new `exec bun run`
pattern. Test coverage expanded from 6 sprite scripts to all 48
agent shims across 8 clouds.

Before: 48 passed, 6 failed
After: 138 passed, 0 failed

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 11:10:37 -05:00
A
55df28137d
feat: convert gcp/ cloud provider from Bash to TypeScript (#1694)
Security review approved. All issues resolved.
2026-02-22 08:51:50 -05:00
A
7227083a61
feat: convert sprite/ cloud provider from Bash to TypeScript (#1692)
* feat: convert sprite/ cloud provider from Bash to TypeScript

Makes Sprite CLI orchestration (retry, org detection, file upload) cleaner.
Converts 381-line lib/common.sh and 6 agent scripts to TS/Bun.

Fixes #1680

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

* fix: add path traversal check, fix regex injection, update test assertions

- Add '..' path traversal rejection in uploadFileSprite
- Replace RegExp constructor with string comparison in createSprite
  to prevent regex injection
- Add base64 output validation in main.ts
- Update TS_CLOUDS sets and test count assertions for sprite conversion

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

* fix: update test assertions for TS-converted cloud providers

Lowered cloud lib/common.sh count from >= 7 to >= 5 and SSH-based
upload_file count from >= 4 to >= 3 to reflect sprite and digitalocean
being converted from Bash to TypeScript.

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

* fix: add temp file path validation in sprite uploadConfigFile

Add path validation to ensure the temp file path stays within the
expected tmpdir() directory, preventing potential path manipulation.

The other three security review findings (path traversal, regex
injection, base64 validation) were already addressed in the previous
commit on this branch.

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

* fix: update test count assertions after sprite TS migration

Both upload-file-security and cloud-lib-source-chain had '>= 5' floor
assertions that assumed sprite had bash lib/common.sh. Now that sprite
is TS-based (no bash lib), the bash-cloud count is 4, not 5.

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

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 05:04:04 -08:00
A
01c6fda39f
feat: convert daytona/ cloud provider from Bash to TypeScript (#1691)
* feat: convert daytona/ cloud provider from Bash to TypeScript

Replaces fragile bash SSH workarounds with structured TypeScript.
Converts 341-line lib/common.sh and 6 agent scripts to TS/Bun.

Fixes #1679

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

* fix: update test assertions for daytona TypeScript conversion

Add daytona to TS_CLOUDS set and lower cloud count thresholds since
daytona no longer has a bash lib/common.sh.

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

* fix: address security review - path traversal, command injection, test counts

- Add path traversal rejection (reject '..') in uploadConfigFile and uploadFile
- Use single quotes around remotePath in shell commands to prevent expansion
- Add strict remotePath validation to uploadConfigFile (allowlist regex)
- Update TS_CLOUDS sets across all test files for daytona TS conversion
- Adjust upload-file-security test count expectations for TS migrations

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

* fix: update test assertions for TS-converted cloud providers

After converting daytona and digitalocean from Bash to TypeScript, the
number of bash-based cloud libs dropped. Updated expected counts:
- cloud-lib-source-chain: >= 6 to >= 5
- cloud-error-guidance create_server: >= 5 to >= 4
- upload-file-security SSH clouds: >= 4 to >= 3
- shared-common-post-session SSH clouds: >= 4 to >= 3

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-02-22 07:53:33 -05:00
A
850327c29d
feat: convert aws/ cloud provider from Bash to TypeScript (#1693)
Migrates AWS Lightsail from 609-line bash (aws/lib/common.sh) to TypeScript,
following the established Fly.io/local provider patterns. Type safety eliminates
SigV4 signing bugs, @clack/prompts provides interactive bundle/region pickers,
and error handling is explicit.

- cli/src/aws/aws.ts — Core: AWS CLI wrapper, SigV4 REST API, auth, provisioning, SSH
- cli/src/aws/agents.ts — Agent configs and install helpers
- cli/src/aws/main.ts — Orchestrator
- aws/*.sh — Converted to thin bun shims with bash fallback (curl|bash compatible)
- cli/package.json — Version bump to 0.6.0

Fixes #1675

Agent: complexity-hunter

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 07:50:54 -05:00
A
5cb36d0328
fix: update test/run.sh after sprite/ lib/common.sh removal (#1695)
**Why:** bash test/run.sh fails immediately with 2 errors in
test_common_source() because sprite/lib/common.sh was deleted when
sprite was converted to TypeScript (PR #1692). The entire shell test
suite was unusable as a result (0 tests could run).

Changes:
- Replace _test_sprite_functions_and_syntax/_test_sprite_log_and_name/
  _test_sprite_remote_source with equivalents that source
  shared/common.sh directly (the actual shared library)
- Remove the per-script integration test loop for sprite scripts since
  they depend on the deleted lib/common.sh
- Update header comment to reflect current test scope

Result: 54 tests pass, 0 fail (was: 2 hard failures + hang).

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 07:50:26 -05:00
A
966f56e813
feat: convert hetzner/ cloud provider from Bash to TypeScript (#1689)
* feat: convert hetzner/ cloud provider from Bash to TypeScript

Migrates hetzner/ to the same TypeScript pattern as fly/ and local/:
- Creates cli/src/hetzner/{main.ts,hetzner.ts,agents.ts}
- Replaces 6 bash agent scripts with thin bun shims
- Reuses cli/src/fly/{oauth.ts,ui.ts} for cross-cloud functionality
- Adds hetzner to TS_CLOUDS in manifest-integrity tests
- Bumps CLI version to 0.5.35

Why: Consistent TypeScript architecture across cloud providers enables
type-safe API interactions, better error handling for Hetzner's unusual
"error: null" success response format, and eliminates bash JSON parsing.

Fixes #1676

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

* fix: validate remotePath in uploadConfigFile to prevent command injection

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

---------

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 03:49:26 -08:00
A
a875e655d9
feat: convert digitalocean/ cloud provider from Bash to TypeScript (#1690)
* feat: convert digitalocean/ cloud provider from Bash to TypeScript

Replaces python3 usage (violates CLAUDE.md) with native TypeScript.
Converts 277-line lib/common.sh and 6 agent scripts to TS/Bun.

Fixes #1677

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

* fix: update tests for digitalocean TypeScript conversion

Add digitalocean to TS_CLOUDS set so bash -n tests skip the removed
lib/common.sh. Skip digitalocean scripts in mock tests (same as fly).
Adjust create_server count threshold from 6 to 5.

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-02-22 06:43:05 -05:00
A
435d9125d5
feat: convert local/ cloud provider from Bash to TypeScript (#1688)
Creates cli/src/local/{main,local,agents}.ts following the Fly.io
pattern. All 6 agent .sh files replaced with thin bun shims.
Extracts shared oauth.ts and ui.ts to cli/src/shared/ for reuse
across cloud providers. Updates fly/ to re-export from shared.

Fixes #1681

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 10:49:40 +00:00
A
24c705cd27
fix: validate env-loaded tokens to prevent curl config injection (#1687)
* fix: validate env-loaded tokens to prevent curl config injection

_load_token_from_env() performed zero validation on API token values
from environment variables before they reached _curl_api(), which
passes auth headers via curl's -K stdin config. A token containing a
double-quote could break out of the config's quoted string and inject
additional curl directives (e.g., redirecting the request to an
attacker-controlled server).

_load_token_from_config() already validates with the same regex
(^[a-zA-Z0-9._/@:+=, -]+$). This adds the same check to the env
path, closing the defense-in-depth gap across all token-loading paths.

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

* feat: pre-built Docker image for OpenClaw on Fly.io (#1686)

Eliminates the slow waitForCloudInit() + bun install phase by booting
a pre-built image with Node.js, bun, and openclaw already installed.
The image is rebuilt daily via GitHub Actions to pick up new releases.

Other agents are unaffected — they still use ubuntu:24.04 + cloud-init.

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

* fix: use positional params in macOS curl path to prevent command injection (#1685)

**Why:** The macOS fallback in `request_missing_cloud_keys()` used
`${providers_json}` directly in a curl `-d` argument. If `providers_json`
contained shell metacharacters (e.g., from a failed python3 call), this
could execute arbitrary commands. The Linux path already used the safe
positional parameter pattern (`bash -c '...' -- "$1" "$2" "$3"`).

Unifies both code paths to use the safe positional parameter pattern.

Fixes #1684

Agent: team-lead

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

* fix: update test to expect rejection of tokens with newlines

The _load_token_from_env validation now rejects tokens containing
newline characters to prevent curl config injection. Update the test
to expect exit code 1 and verify the warning message is emitted.

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.6 <noreply@anthropic.com>
Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
2026-02-22 03:51:30 -05:00
A
26267ac9ff
fix: use positional params in macOS curl path to prevent command injection (#1685)
**Why:** The macOS fallback in `request_missing_cloud_keys()` used
`${providers_json}` directly in a curl `-d` argument. If `providers_json`
contained shell metacharacters (e.g., from a failed python3 call), this
could execute arbitrary commands. The Linux path already used the safe
positional parameter pattern (`bash -c '...' -- "$1" "$2" "$3"`).

Unifies both code paths to use the safe positional parameter pattern.

Fixes #1684

Agent: team-lead

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-22 02:54:32 -05:00
A
0f4df7be71
feat: pre-built Docker image for OpenClaw on Fly.io (#1686)
Eliminates the slow waitForCloudInit() + bun install phase by booting
a pre-built image with Node.js, bun, and openclaw already installed.
The image is rebuilt daily via GitHub Actions to pick up new releases.

Other agents are unaffected — they still use ubuntu:24.04 + cloud-init.

Co-authored-by: spawn-bot <spawn-bot@openrouter.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 02:50:46 -05:00
A
e381ca2412
fix: replace require() with ESM imports in bun eval scripts (#1682)
* fix: replace require() with ESM imports in bun eval scripts (#1669)

Fixes #1669

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

* fix: update test assertion to match ESM import pattern

The test expected require('http') but the PR changed shared/common.sh
to use ESM imports. Update assertion to expect import http from 'http'.

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-02-22 01:50:08 -05:00
A
9f43113452
fix: pin zeroclaw installer to commit SHA instead of mutable main branch (#1683)
Replaces all references to zeroclaw-labs/zeroclaw/main/scripts/install.sh
with a pinned commit SHA (a117be64). This prevents supply chain attacks via
the mutable 'main' branch reference in curl|bash installer patterns.

Other curl|bash patterns (bun.sh, claude.ai, sprites.dev) use HTTPS to
vendor-controlled domains with no stable commit SHA to pin to -- these
follow industry-standard installer patterns and are left as-is.

Fixes #1670

-- refactor/ux-engineer

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 01:49:44 -05:00