spawn/.github/workflows/packer-snapshots.yml
Ahmed Abushagur d77a067aa4
fix: snapshot cleanup + claude install (name-prefix filter) (#2273)
* fix: claude snapshot build — remove npm fallback from install command

The native install (curl | bash) succeeds but exits non-zero due to a
PATH warning. The || fallback then tries `npm install` which doesn't
exist on the "minimal" tier → exit 127.

Fix: replace npm fallback with binary existence check (same pattern
as hermes agent). If install exits non-zero but ~/.local/bin/claude
exists, the build succeeds.

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

* fix: snapshot cleanup and lookup — use name prefix instead of tags

DO Packer builder `tags` only apply to the temporary build droplet,
not the resulting snapshot image. Both the workflow cleanup step and
the CLI's findSpawnSnapshot() were querying by `tag_name` which
returned nothing — old snapshots piled up and the CLI couldn't find
existing snapshots.

Fix: filter by snapshot name prefix (`spawn-{agent}-`) instead of
tags, in both the workflow and the CLI. Remove misleading `tags`
from the Packer template. Add test cases for name-prefix filtering.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:32:58 -08:00

107 lines
3.3 KiB
YAML

name: Packer DO Snapshots
on:
schedule:
# Nightly at 4 AM UTC (before tarball build at 5 AM)
- cron: "0 4 * * *"
workflow_dispatch:
inputs:
agent:
description: "Single agent to build (leave empty for all)"
required: false
type: string
permissions:
contents: read
jobs:
matrix:
name: Generate matrix
runs-on: ubuntu-latest
outputs:
agents: ${{ steps.set.outputs.agents }}
steps:
- uses: actions/checkout@v4
- id: set
run: |
SINGLE_AGENT="${SINGLE_AGENT_INPUT}"
if [ -n "$SINGLE_AGENT" ]; then
echo "agents=[\"${SINGLE_AGENT}\"]" >> "$GITHUB_OUTPUT"
else
AGENTS=$(jq -c 'keys' packer/agents.json)
echo "agents=${AGENTS}" >> "$GITHUB_OUTPUT"
fi
env:
SINGLE_AGENT_INPUT: ${{ inputs.agent }}
build:
name: "Build ${{ matrix.agent }}"
needs: matrix
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
agent: ${{ fromJson(needs.matrix.outputs.agents) }}
steps:
- uses: actions/checkout@v4
- name: Read agent config
id: config
run: |
TIER=$(jq -r --arg a "$AGENT_NAME" '.[$a].tier // "minimal"' packer/agents.json)
INSTALL=$(jq -c --arg a "$AGENT_NAME" '.[$a].install // []' packer/agents.json)
echo "tier=${TIER}" >> "$GITHUB_OUTPUT"
echo "install=${INSTALL}" >> "$GITHUB_OUTPUT"
env:
AGENT_NAME: ${{ matrix.agent }}
- name: Setup Packer
uses: hashicorp/setup-packer@main
with:
version: latest
- name: Init Packer plugins
run: packer init packer/digitalocean.pkr.hcl
- name: Generate variables file
run: |
jq -n \
--arg token "$DO_API_TOKEN" \
--arg agent "$AGENT_NAME" \
--arg tier "$TIER" \
--argjson install "$INSTALL_COMMANDS" \
'{
do_api_token: $token,
agent_name: $agent,
cloud_init_tier: $tier,
install_commands: $install
}' > packer/auto.pkrvars.json
env:
DO_API_TOKEN: ${{ secrets.DO_API_TOKEN }}
AGENT_NAME: ${{ matrix.agent }}
TIER: ${{ steps.config.outputs.tier }}
INSTALL_COMMANDS: ${{ steps.config.outputs.install }}
- name: Build snapshot
run: packer build -var-file=packer/auto.pkrvars.json packer/digitalocean.pkr.hcl
env:
PACKER_LOG: "1"
- name: Cleanup old snapshots
if: success()
run: |
# DO snapshots don't support tags — filter by name prefix instead
PREFIX="spawn-${AGENT_NAME}-"
SNAPSHOTS=$(curl -s -H "Authorization: Bearer ${DO_API_TOKEN}" \
"https://api.digitalocean.com/v2/images?private=true&per_page=100" \
| jq -r --arg prefix "$PREFIX" \
'[.images[] | select(.name | startswith($prefix))] | sort_by(.created_at) | reverse | .[1:] | .[].id')
for ID in $SNAPSHOTS; do
echo "Deleting old snapshot: ${ID}"
curl -s -X DELETE -H "Authorization: Bearer ${DO_API_TOKEN}" \
"https://api.digitalocean.com/v2/images/${ID}" || true
done
env:
DO_API_TOKEN: ${{ secrets.DO_API_TOKEN }}
AGENT_NAME: ${{ matrix.agent }}