MODAL_SANDBOX_ID and sandbox name were interpolated directly into
Python code strings, allowing potential code injection. Now all
user-controlled values are passed via environment variables and
read with os.environ in Python.
Changes:
- create_server: pass name/image via _MODAL_NAME/_MODAL_IMAGE env vars,
use getattr() for image lookup, add sandbox name validation
- run_server: pass sandbox ID and command via env vars
- interactive_session: pass sandbox ID and command via env vars
- destroy_server: pass sandbox ID via env var
- Add validate_sandbox_id() to enforce sb-<alphanumeric> format
- upload_file: remove printf '%q' escaping (base64 is safe)
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix triple-quote injection in SSH keys (Scaleway, UpCloud), userdata
(BinaryLane), init scripts (Civo, Kamatera), and GraphQL queries
(RunPod) by passing data via stdin/json_escape instead of inline
string interpolation
- Add input validation for all cloud provider env vars (region, type,
plan, etc.) using validate_region_name/validate_resource_name to block
shell metacharacters before they reach Python string interpolation
- Validate Modal image name as Python identifier to prevent code injection
- Validate numeric env vars (RAM, GPU count, disk size) across all providers
Affects: 19 cloud provider lib/common.sh files
Agent: security-auditor
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show clear error when --prompt/-p or --prompt-file is used without a
value (previously silently ignored)
- Fix --prompt-file splice index bug when used after --prompt
- Replace echo -e with printf in fly/lib/common.sh for macOS bash 3.x
compatibility
- Fix incorrect env var name in README (DIGITALOCEAN_TOKEN -> DO_API_TOKEN)
- Add missing agent entries (gptme, OpenCode, Plandex) to 11 cloud READMEs
- Add all 13 agents to Civo README (previously only had 3)
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enhance UX across CLI and cloud providers with actionable error messages:
1. Modal sandbox creation failures now provide:
- Detailed error output from Python SDK
- Common causes (auth expired, quota issues, network)
- Step-by-step troubleshooting (re-auth, check quota, status page)
2. Modal CLI installation failures now explain:
- Missing pip/pip3 (with installation commands)
- Permission issues (suggest --user flag)
- Manual installation steps
3. Sprite connectivity failures now include:
- Commands to check sprite status and logs
- Steps to recreate sprite
- Support contact information
4. Sprite CLI installation now:
- Catches installation failures with helpful error messages
- Verifies installation succeeded before proceeding
- Provides manual installation instructions
5. CLI script download failures improved:
- Distinguish between 404 (doesn't exist) and other errors
- Provide specific next steps for each scenario
- Suggest checking matrix for implementation status
All error messages follow the pattern:
- What went wrong
- Why it might have happened (common causes)
- What to do next (actionable steps)
Agent: ux-engineer
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Security:
- Fix command injection in modal/lib/common.sh (run_server, upload_file, interactive_session)
- Fix command injection in fly/lib/common.sh (run_server, upload_file, interactive_session)
- All container providers now use printf '%q' for proper shell escaping
Complexity:
- Extract _api_should_retry_on_error() helper in shared/common.sh (-19 lines)
- Refactor scaleway_api and upcloud_api to use shared retry helper (-24 lines)
- Extract _save_fly_token() helper in fly/lib/common.sh (-11 lines)
- Extract validateAndGetAgent() in commands.ts, reducing cmdRun/cmdAgentInfo duplication
- Refactor cmdList column width calculation to use calculateColumnWidth()
UX:
- Add actionable next steps to error messages in shared/common.sh
- Improve CLI bash fallback error messages with guidance (spawn.sh)
- Add OAuth progress indicator during browser authentication wait
- Show invalid model ID value and link to openrouter.ai/models
- Add troubleshooting steps for agent installation failures
Tests:
- Update test assertions in test/run.sh to match refactored patterns
- All tests passing: 74 TypeScript + 75 bash = 149 total, 0 failures
Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The upstream OpenCode installer pipes `curl -# -L | tar xz` which fails
in container exec environments (Sprite, E2B, Modal, Daytona) where the
binary stream gets corrupted through the exec layer, producing
"gzip: stdin: not in gzip format" errors.
Added opencode_install_cmd() to shared/common.sh that downloads the
binary to a file first, then extracts it. Updated all 17 opencode.sh
scripts to use this robust method instead of the upstream installer.
The previous fix (#44) only addressed Sprite with a hardcoded
linux-x86_64 architecture. This fix detects OS/arch dynamically and
applies to all cloud providers.
Fixes#42
Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Plandex is an open source AI coding agent for complex tasks (15k+ GitHub
stars, multiple HN frontpage posts). It natively supports OpenRouter via
OPENROUTER_API_KEY environment variable and installs via a single curl
command. Go-based CLI with sandbox and version control for AI changes.
Implemented on all 14 clouds: sprite, hetzner, digitalocean, vultr,
linode, lambda, aws-lightsail, gcp, e2b, modal, fly, civo, scaleway,
daytona.
Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Completed ENV_TEMP pattern extraction across remaining providers:
1. Modal: gptme.sh (1 script) - uses inject_env_vars_local
2. GCP: all 10 agent scripts - uses inject_env_vars_ssh
3. Fly.io: all 11 agent scripts - uses new inject_env_vars_fly
- Added inject_env_vars_fly() to fly/lib/common.sh
- Handles both .bashrc and .zshrc (Fly-specific requirement)
4. Sprite: amazonq, cline, gemini (3 scripts) - uses inject_env_vars_sprite
Total scripts converted in this commit: 25
Total scripts converted in Round 25 Task #1: 78 scripts
Each conversion replaces 11-15 lines of temp file management with a single
function call that handles creation, permissions, content generation, upload,
sourcing, and cleanup.
The only remaining ENV_TEMP patterns are DOTENV_TEMP in nanoclaw scripts,
which are agent-specific .env files and should remain as-is.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add gptme agent to spawn matrix
Add gptme (https://github.com/gptme/gptme) - a personal AI agent in the
terminal with tools for code editing, terminal commands, web browsing,
and more. Natively supports OpenRouter via OPENROUTER_API_KEY.
- Add gptme agent entry to manifest.json with OpenRouter env vars
- Implement sprite/gptme.sh deployment script
- Implement hetzner/gptme.sh deployment script
- Add "missing" matrix entries for remaining 8 clouds
- Update README.md with usage instructions for Sprite and Hetzner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Fly.io cloud provider with claude and aider agents
Add Fly.io as a new cloud provider using the Machines REST API for
provisioning and flyctl CLI for SSH access. Docker-based machines
with pay-per-second pricing.
- Create fly/lib/common.sh with Fly.io Machines API integration
- Implement fly/claude.sh for Claude Code deployment
- Implement fly/aider.sh for Aider deployment
- Update README.md with Fly.io usage instructions and env vars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add gemini, amazonq, cline, gptme to Fly.io
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add openclaw, nanoclaw, goose, codex, interpreter to Fly.io
Implements 5 new agent scripts for the Fly.io cloud provider:
- fly/openclaw.sh: OpenClaw with gateway + TUI, model selection, config
- fly/nanoclaw.sh: NanoClaw WhatsApp agent with .env configuration
- fly/goose.sh: Block's Goose agent with OpenRouter provider
- fly/codex.sh: OpenAI Codex CLI with OpenRouter base URL override
- fly/interpreter.sh: Open Interpreter with OpenRouter base URL override
All scripts follow the Fly.io pattern (flyctl-based, no IP args for
run_server/interactive_session) and use upload_file for env injection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add gptme agent to 8 remaining clouds
Implement gptme agent scripts for digitalocean, vultr, linode, lambda,
aws-lightsail, gcp, e2b, and modal. Each script follows the exact
pattern of that cloud's existing aider.sh, adapted for gptme's install
and launch commands. Updates manifest.json matrix entries from "missing"
to "implemented".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add guardrails from insights: CLAUDE.md rules, hooks, pre-commit
Based on usage insights analysis:
CLAUDE.md:
- Shell script rules: curl|bash compat, macOS bash 3.x compat
- Autonomous loop rules: test after each iteration, never revert fixes
- Git workflow rules: always use feature branches
.claude/settings.json:
- PostToolUse hook validates .sh files on every Write/Edit:
syntax check, no relative source, no echo -e, no set -u
.githooks/pre-commit:
- Blocks commits with: syntax errors, relative sources, echo -e,
set -euo, references to deleted functions
- Install: git config core.hooksPath .githooks
README.md:
- Added developer setup section with hook installation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixed SC2016 warnings by removing unnecessary backslashes from ${HOME}
and ${PATH} variables inside single-quoted strings in e2b and modal
provider libraries. Variables inside single quotes don't expand, so
the backslashes were literal characters being written to config files.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add trap 'rm -f "${ENV_TEMP}"' EXIT after mktemp creation
- Scripts with DOTENV_TEMP get combined trap for both files
- Remove manual rm calls that are now redundant
- Prevents temp file leaks on early script exit (errors, signals)
- Affects 67 agent scripts across all providers
Impact: Prevents /tmp pollution in production deployments
Score: 90 (Impact: 9, Confidence: 10, Risk: 1)
- Add shellcheck source comments to all agent scripts
- Tells shellcheck where provider-exported variables are defined
- Fix 132+ SC2154 warnings across all providers
Score: 30 (Impact: 6, Confidence: 10, Risk: 2)
All modal scripts now validate that create_server() succeeded and that
MODAL_SANDBOX_ID is set before proceeding with setup steps. This prevents
silent failures when Modal sandbox creation fails.
Changes:
- Added explicit error checking after create_server() call
- Added validation that MODAL_SANDBOX_ID is not empty
- Applied to all 10 modal scripts (nanoclaw, aider, claude, interpreter,
cline, amazonq, gemini, openclaw, goose, codex)
Fixes SC2154 shellcheck warnings for unassigned MODAL_SANDBOX_ID variable.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Protects against 'unbound variable' errors even if set -u is
re-enabled or inherited. Every [[ -n "$UPPER_VAR" ]] pattern now
uses [[ -n "${UPPER_VAR:-}" ]] to safely default to empty.
Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues broke the OAuth callback server on macOS:
1. echo -e doesn't work in bash 3.x — \r\n appears as literal text
in the HTTP response, browser gets malformed headers.
Fix: pre-write response with printf to a file before the subshell.
2. local variables inside ( ... ) & subshell — undefined behavior in
bash 3.x since subshells aren't function scope.
Fix: use plain variables in subshells.
3. ((elapsed++)) when elapsed=0 evaluates to falsy — set -e kills
the script on the first iteration of the timeout loop.
Fix: use elapsed=$((elapsed + 1)) instead.
Also simplified nc_listen detection to only check for BusyBox
(the -p flag check could misfire on macOS nc).
Applied to all 10 lib/common.sh files.
Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macOS ships bash 3.x which doesn't support nested process substitution.
When scripts are run via `bash <(curl ...)`, the inner `source <(curl ...)`
for loading common.sh fails silently, causing "command not found" errors.
Fix: replace `source <(curl -fsSL URL)` with `eval "$(curl -fsSL URL)"`
across all 100 agent scripts. eval+curl works on bash 3.x and newer.
Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>