CI Orphan Workflow Cleanup
Context
When a workflow file is deleted from .github/workflows/ on main, GitHub
Actions does NOT automatically deregister the workflow. It lives on in the
Actions UI and in the REST API listing with state: deleted until either:
- All of its historical
workflow_runsare deleted, OR - A new file with the SAME
pathis committed.
Between deletion of the file and deregistration, the workflow will still be counted in reporting dashboards (billing minutes, success-rate stats, etc.) and may appear to be "running" in historical listings. Any new pushes will NOT trigger the deleted workflow (the path no longer resolves), but older failed runs will remain visible and inflate wall-time totals in analytics.
Reference: https://github.com/orgs/community/discussions/40350
When to run
Run this runbook when a workflow file has been deleted from main but still
appears in gh api repos/OWNER/REPO/actions/workflows with a path that no
longer exists on disk — typically surfaced by a CI cost-analysis pass.
Identify the orphan
export GH_TOKEN=$(python3 scripts/gh-token.py devops-engineer)
# List registered workflows, compare paths against .github/workflows/*.yml on main
gh api repos/upsquad-ai/upsquad-core/actions/workflows --jq \
'.workflows[] | {id, name, path, state}'
# For any workflow whose path is not on main AND not on an active feature
# branch, fetch its individual record to confirm state: deleted
gh api repos/upsquad-ai/upsquad-core/actions/workflows/<ID>
A workflow on a feature branch that has not yet merged to main is NOT an
orphan — it will continue to trigger for PRs from that branch and is load-
bearing for the PR's required-checks gate.
Purge procedure
Deleting all runs forces GitHub to drop the workflow from the registered list. This is the only supported path — there is no REST endpoint to delete a workflow record directly.
WORKFLOW_ID=<id>
REPO=upsquad-ai/upsquad-core
# Loop until no runs remain. Each iteration pulls one page of 100.
while :; do
RUN_IDS=$(gh api "repos/$REPO/actions/workflows/$WORKFLOW_ID/runs?per_page=100" \
--jq '.workflow_runs[].id')
[ -z "$RUN_IDS" ] && break
for id in $RUN_IDS; do
gh api -X DELETE "repos/$REPO/actions/runs/$id"
done
done
# Confirm the workflow is gone from the listing
gh api repos/$REPO/actions/workflows --jq \
'.workflows[] | select(.id == '"$WORKFLOW_ID"')'
# (should print nothing)
Rollback
If a workflow was deleted in error, restore it by committing the YAML file
back to .github/workflows/ on a branch and opening a PR. GitHub registers a
fresh workflow on merge (with a new id) and run history is NOT recovered —
the purge is terminal for historical data.
Historical cleanup log
| Date | Workflow ID | Name | Path | Source commit | Runs purged |
|---|---|---|---|---|---|
| 2026-04-20 | 258668771 | Build & Test | .github/workflows/build-context-engine.yml | deleted in PR #606 (be89510) | ~400 |
The build-context-engine.yml workflow was superseded by build-images.yml
(matrixed multi-service build). After the file was deleted on 2026-04-17, the
workflow continued to appear in gh api with state: deleted and its 400
historical runs (19% success rate post-deletion, as failing runs accumulated
during the brief period before GitHub stopped triggering) inflated CI
cost-analysis reports. Purging the runs removed it from the registered list.
Refs: #727 (tracking), PRD #725 §4.0 Phase 1 CI cleanup, C1 + C2.