azure/azure-stack-destroy
Tear down a Git-Ape deployment by ID. Reads `state.json` under `.azure/deployments/<id>/` to delete the Azure Deployment Stack and purge soft-deleted Key Vault / Cognitive Services. Refuses to run without `state.json`. Use for any local CLI or VS Code Git-Ape teardown so the result matches the CI destroy workflow.
npx skills add https://github.com/Azure/git-ape --skill azure-stack-destroy
Destroy a Git-Ape deployment by deleting its subscription-scoped Azure Deployment Stack in a single idempotent call (az stack sub delete --action-on-unmanage deleteAll --bypass-stack-out-of-sync-error true). The stack owns every resource the matching deploy created — across resource groups and subscription scope — so one delete cleans up everything.
After the stack is gone, this skill performs a soft-delete purge sweep for resource types that linger after deletion (Key Vault, Cognitive Services, App Configuration, API Management, ML workspaces, Recovery Services vaults). Resources flagged purgeProtected: true in state.json are intentionally retained.
This skill mirrors .github/workflows/git-ape-destroy.yml so local destroys and CI destroys are interchangeable.
Trigger this skill when the user wants to tear down a Git-Ape deployment they previously created:
state.json is still on diskgit-ape-destroy.yml does in CIaz group deleteFor any deployment Git-Ape created, this skill is the correct tool — do not suggest az group delete on its own. Reasons:
az group delete cleans only one RG.state.json and metadata.json to terminal status (destroyed, retained-soft-deleted, etc.) so the next operation sees an accurate view.Refuse to invoke this skill in any of these cases:
state.json on disk. Hard prerequisite — see below. Without it, recommend re-running deploy or aborting.az group create). Suggest az group delete --name <rg> --yes directly instead.azure-stack-deploy for those.azure-stack-deploy — same stack, same state.json key (stackId)az group delete against a Git-Ape deployment (don't — you'll miss soft-delete cleanup and multi-RG resources)| Tool | Why |
|------|-----|
| az (Azure CLI ≥ 2.59) | az stack sub delete --bypass-stack-out-of-sync-error requires a recent CLI |
| jq | Read state.json |
| bash ≥ 4 OR PowerShell 7+ | Either runner works |
| Active az login | Must be the same subscription where the stack lives |
| Existing state.json under .azure/deployments/<id>/ | Source of truth for stackId, managedResources, softDeletable, purgeProtected |
> Hard prerequisite: state.json under .azure/deployments/<id>/. Without it this skill aborts — it has no idea which stack, resource groups, or soft-deletables to clean up. Do NOT hand-write state.json; re-run the matching azure-stack-deploy for that deployment ID first, or use az group delete directly on a known resource group (a non-Git-Ape teardown, outside this skill's scope).
The scripts default to fast mode (interactive default). The CI workflow keeps sync mode (deterministic).
| | How | Wait time (small VNet stack) | When to use |
|--|--|--|--|
| Fast (default) | Background the az stack sub delete call, then poll managed RGs with az group exists | ~2 min | Local CLI / VS Code use; user wants quick feedback |
| Sync (--wait / -Wait) | az stack sub delete ... --yes (blocks until stack metadata is fully cleaned) | ~5 min | CI pipelines (default in git-ape-destroy.yml); when you need every Azure-side cleanup completed before the script exits |
The Azure CLI does not expose --no-wait on az stack sub delete, so the fast path runs the same command as a detached background process. In fast mode the stack-metadata cleanup continues asynchronously in Azure after the script returns. The next destroy of the same deploymentId is idempotent: if the stack is still finalizing, az stack sub show will return it and the script will simply pick up where Azure left off.
DEPLOYMENT_ID="deploy-20260506-001"
DEPLOYMENT_PATH=".azure/deployments/$DEPLOYMENT_ID"
[[ -f "$DEPLOYMENT_PATH/state.json" ]] || { echo "state.json missing — cannot destroy"; exit 1; }
.github/skills/azure-stack-destroy/scripts/destroy-stack.sh \
--deployment-id "$DEPLOYMENT_ID"
Skip the confirmation prompt (use only in automation):
.github/skills/azure-stack-destroy/scripts/destroy-stack.sh \
--deployment-id "$DEPLOYMENT_ID" \
--yes
Force CI-equivalent sync wait (default for the CI workflow; opt-in for the script):
.github/skills/azure-stack-destroy/scripts/destroy-stack.sh \
--deployment-id "$DEPLOYMENT_ID" \
--yes --wait
PowerShell equivalents:
.github/skills/azure-stack-destroy/scripts/destroy-stack.ps1 -DeploymentId "$DEPLOYMENT_ID"
.github/skills/azure-stack-destroy/scripts/destroy-stack.ps1 -DeploymentId "$DEPLOYMENT_ID" -Yes
.github/skills/azure-stack-destroy/scripts/destroy-stack.ps1 -DeploymentId "$DEPLOYMENT_ID" -Yes -Wait
state.json and extracts stackId, deployMethod, resourceGroup, managedResources[], softDeletable[]destroy confirmation (unless --yes)stackId present):az stack sub delete --action-on-unmanage deleteAll --bypass-stack-out-of-sync-error true --yesstackId, only resourceGroup): az group delete --name <rg> --yessoftDeletable resource not marked purgeProtected:az keyvault list-deleted + az keyvault purgeaz cognitiveservices account purgepurgeResults[] with status: skipped-natural-expiryaz deployment sub delete) to stay under the 800/scope limitstate.json and metadata.json with terminal status:| Status | Meaning |
|--------|---------|
| destroyed | Stack/RG gone and all soft-deletables purged or absent |
| retained-soft-deleted | Stack gone but at least one soft-deletable retained (purge-protected or purge failed) |
| partially-destroyed | Stack delete partially failed |
| destroy-failed | Stack/RG delete failed entirely |
| already-destroyed | Stack and RG were already gone before this call |
=== Destroy Summary ===
Status: destroyed
Duration: 87s
=======================
Or, when something is intentionally retained:
=== Destroy Summary ===
Status: retained-soft-deleted
Duration: 92s
Retained: 1 soft-deleted resource(s) (purge-protected)
=======================
state.json gains destroyedAt, destroyedBy, destroyDuration, and a purgeResults[] array describing each soft-deletable's outcome.
| Flag (bash) | Param (pwsh) | Required | Description |
|-------------|--------------|----------|-------------|
| --deployment-id <id> | -DeploymentId <id> | yes | Folder name under .azure/deployments/ |
| --yes | -Yes | no | Skip the typed destroy confirmation prompt (CI-only) |
| --wait | -Wait | no | Sync mode: block until Azure has cleaned up stack metadata. Matches the CI workflow. Slower (~3-4×) but fully deterministic. |
| --poll-timeout <sec> | -PollTimeout <sec> | no | Fast-mode timeout per managed RG poll (default 600s) |
| Symptom | Likely cause | Recovery |
|---------|--------------|----------|
| state.json missing | Deployment never reached the state-write phase, or was hand-edited | Re-deploy (idempotent on stack name) then destroy, OR delete the .azure/deployments/<id>/ folder if Azure has nothing |
| Stack out of sync despite --bypass-stack-out-of-sync-error | Old CLI version | Upgrade az to ≥ 2.59 |
| Key Vault purge fails | Vault is purge-protected (purgeProtected: true) | Expected — wait 7-90 days for soft-delete window to expire, or purge manually after disabling protection |
| Cannot delete resource group …/InUseSubnetCannotBeDeleted | A resource outside the stack references one inside (e.g. external subnet peered to a deleted VNet) | Inspect externalReferences[] in state.json; remove the reference and rerun |
azure-stack-deploy — the matching deploy skill (writes the state.json this skill consumes)azure-drift-detector — check for unmanaged drift BEFORE destroyazure-resource-visualizer — visualize what's in the stack before tearing it downTake azure/azure-stack-destroy from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.