From spindev-deploy
Use when running any `sprite` CLI command or calling the sprites.dev API from Windows/Git Bash — prevents path mangling, flag-ordering bugs, and large-file upload failures. Trigger on any mention of Fly.io Sprites, sprites.dev, `sprite exec`, `sprite api`, `sprite console`, `sprite checkpoint`, `sprite url`, uploading files into a sprite, or deploying/restarting a service in a sprite.
How this skill is triggered — by the user, by Claude, or both
Slash command
/spindev-deploy:sprites-devThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill exists because every rule below comes from a real failure. The `sprite` CLI works fine on Linux and macOS; on Windows under Git Bash it silently mangles paths and flags in ways that look like the remote did something weird. Follow the rules here and those classes of bugs go away.
This skill exists because every rule below comes from a real failure. The sprite CLI works fine on Linux and macOS; on Windows under Git Bash it silently mangles paths and flags in ways that look like the remote did something weird. Follow the rules here and those classes of bugs go away.
Git Bash on Windows auto-converts anything that looks like a Unix path into a Windows path before the child process sees it. That silently breaks sprite in three ways:
| What you write | What Git Bash actually sends | Result |
|---|---|---|
sprite exec -- uname -a | sprite exec -- uname -a | -a parsed as a sprite flag, not uname's |
sprite exec -- cat /etc/os-release | sprite exec -- cat C:/Program Files/Git/etc/os-release | File not found inside the sprite |
sprite api /v1/sprites/rd-demo | sprite api C:/Program Files/Git/v1/sprites/rd-demo | Malformed URL |
sprite exec --dir /app -- cmd | sprite exec --dir C:/Program Files/Git/app -- cmd | chdir fails inside the sprite |
bash -cNever pass flags or paths directly to sprite exec. Go through bash -c so the remote bash owns parsing:
# WRONG — flags get misinterpreted
sprite exec -- uname -a
sprite exec -- python3 --version
# RIGHT — bash -c protects everything
sprite exec -- bash -c "uname -a"
sprite exec -- bash -c "python3 --version"
sprite exec -- bash -c "cat /etc/os-release"
sprite exec -- bash -c "cd /app && pip install -r requirements.txt"
MSYS_NO_PATHCONV=1 for every API callsprite api takes a URL path as its first argument. Git Bash mangles it unless path conversion is disabled:
# WRONG — path gets mangled
sprite api /v1/sprites/rd-demo/services
# RIGHT — disable path conversion
MSYS_NO_PATHCONV=1 sprite api /v1/sprites/rd-demo/services
--, then curl flagsThe syntax is sprite api <path> -- [curl-options]. Curl flags like -X PUT go after --:
# WRONG — -X parsed as a sprite flag
sprite api -X PUT /v1/sprites/rd-demo/services/web
MSYS_NO_PATHCONV=1 sprite api -- -X PUT /v1/sprites/rd-demo/services/web
# RIGHT — path first, then --, then curl flags
MSYS_NO_PATHCONV=1 sprite api /v1/sprites/rd-demo/services/web -- -X PUT \
-H "Content-Type: application/json" \
-d '{"cmd":"python3","args":["app.py"]}'
The --file flag uses a source:dest format. Windows drive letters (C:) collide with the : separator:
# WRONG — C: conflicts with source:dest separator
sprite exec --file "C:/Users/User/data/geo.db:/app/data/geo.db" -- echo ok
# RIGHT — cd to the source directory first, use a relative path
cd data/
sprite exec --file "geo.db:/app/data/geo.db" -- bash -c "ls -lh /app/data/geo.db"
Files over roughly 20 MB can hit HTTP 502 during upload. Compress first, decompress inside the sprite:
cd data/
gzip -k -9 geo.db # creates geo.db.gz locally
sprite exec --file "geo.db.gz:/app/data/geo.db.gz" -- \
bash -c "cd /app/data && gzip -d geo.db.gz && ls -lh geo.db"
rm geo.db.gz # clean up local copy
--dir entirely; cd inside bash -cThe --dir flag value is path-mangled on Windows. Do the cd inside the remote bash instead:
# WRONG — /app becomes C:/Program Files/Git/app
sprite exec --dir /app -- pip install flask
# RIGHT
sprite exec -- bash -c "cd /app && pip install flask"
MSYS_NO_PATHCONV=1 sprite api /v1/sprites/SPRITE_NAME/services/SERVICE_NAME -- \
-X PUT -H "Content-Type: application/json" \
-d '{
"cmd": "python3",
"args": ["app.py"],
"dir": "/app",
"http_port": 5000,
"env": {"KEY": "value"}
}'
Service fields: cmd (string), args (string[]), dir (string), http_port (number), env (map), needs (string[] — dependencies).
# List
MSYS_NO_PATHCONV=1 sprite api /v1/sprites/SPRITE_NAME/services
# Logs
MSYS_NO_PATHCONV=1 sprite api /v1/sprites/SPRITE_NAME/services/SERVICE_NAME/logs
# Restart via stop + start (the restart endpoint is unreliable)
MSYS_NO_PATHCONV=1 sprite api /v1/sprites/SPRITE_NAME/services/SERVICE_NAME/stop -- -X POST
MSYS_NO_PATHCONV=1 sprite api /v1/sprites/SPRITE_NAME/services/SERVICE_NAME/start -- -X POST
sprite url # show URL (deprecated; prefer `sprite info`)
sprite url update --auth public # anyone can access
sprite url update --auth sprite # org members only
sprite checkpoint create --comment "description"
sprite checkpoint list
sprite restore v1 # restore to named checkpoint
The six-step pattern for standing up a short-lived preview of a repo-hosted app on a public sprites.dev URL. Each app layers its own file list, entrypoint, env, and auth on top:
sprite create <name> then sprite use <name>
(drops a .sprite marker in cwd; gitignore it).--file local:remote flags on a
single sprite exec, with a cheap sanity-check command after the
-- (e.g. bash -c "ls /app && python3 -c 'import app'").SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))") — never commit, never reuse across
sprites.sprite api /v1/sprites/<name>/services/<svc> -- -X PUT -H "Content-Type: application/json" -d '{...}' with
cmd, args, dir, http_port, and any per-deploy env secrets.
Re-running the PUT replaces the service — that's the iterate loop.sprite url update --auth public for external
preview, or keep sprite (default) for org-only. Pair public
with an app-layer auth gate; don't run unauthed on an indexable URL.sprite info (preferred; sprite url is
deprecated).Teardown — missing from the quick-ref table below:
sprite destroy <name> --force # --force skips the TTY prompt; scripting-safe
For a worked example of this flow against a real invite-gated Python
app, see TodSmith shared/runbooks/sprites-deploy.md.
| Task | Command |
|---|---|
| Run command in sprite | sprite exec -- bash -c "command" |
| Interactive shell | sprite console |
| Upload file | cd dir/ && sprite exec --file "file:/dest/file" -- bash -c "ls /dest/file" |
| API GET | MSYS_NO_PATHCONV=1 sprite api /v1/sprites/NAME/endpoint |
| API PUT/POST | MSYS_NO_PATHCONV=1 sprite api /v1/sprites/NAME/endpoint -- -X PUT -d '{...}' |
| Set active sprite | sprite use SPRITE_NAME |
| List sprites | sprite list |
If a rule here turns out to be wrong (upstream fixed something, new failure mode discovered), update this file. Every rule should trace back to an actual failure — when adding a new one, prefer a one-line note about the failure that produced it over a long rationale.
This skill is served from a read-only plugin cache (~/.claude/plugins/cache/spindev-deploy@spinlockdevelopment/<version>/). Edits there do not persist and do not propagate. To actually apply the fix, edit the authoritative copy at plugins/spindev-deploy/skills/sprites-dev/SKILL.md in a clone of spinlockdevelopment/dev-setup, commit (bringup: straight to main; protected: feature branch + PR), and push. Consumers pick it up on their next /plugin marketplace update.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
npx claudepluginhub spinlockdevelopment/dev-setup --plugin spindev-deploy