用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/Morrison-Lab/ai-config --skill cancel-superseded命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | cancel-superseded |
| description | Cancel superseded CI pipelines. |
| user-invocable | true |
| allowed-tools | ["Bash"] |
Cancel older pipelines on the same branch that have been superseded by a newer push, freeing CI runners for the latest pipeline.
GitLab-only. This skill drives
glabagainst GitLab CI pipelines. There is no GitHub equivalent here — on a GitHub repo, cancel superseded runs withgh run cancel <run-id>instead (or rely on per-PRconcurrencyin the workflow, which auto-cancels superseded runs).
pending$PROJECT_ID instead of a manual placeholder):PROJECT_ID="$(glab api "projects?search=$(basename "$(git rev-parse --show-toplevel)")" 2>/dev/null | \
python3 -c "import json,sys; print(json.load(sys.stdin)[0]['id'])")"
echo "PROJECT_ID=$PROJECT_ID"
If the search returns more than one project (same repo name in different
groups), set PROJECT_ID by hand from glab api "projects?search=<name>".
BRANCH=$(git branch --show-current)
# Get pipelines for this branch, newest first
glab api "projects/$PROJECT_ID/pipelines?ref=$BRANCH&sort=desc&per_page=10" | \
python3 -c "
import json, sys
try: pipelines = json.load(sys.stdin)
except json.JSONDecodeError: sys.exit(1) # glab error already on stderr; don't add a traceback
for p in pipelines:
print(f'{p[\"id\"]:>6} {p[\"status\"]:12s} {p[\"ref\"]}')
" | cat
running/pending/created is a
cancel candidate. This step only prints — nothing is canceled yet:glab api "projects/$PROJECT_ID/pipelines?ref=$BRANCH&sort=desc&per_page=10" | \
python3 -c "
import json, sys
try: pipelines = json.load(sys.stdin)
except json.JSONDecodeError: sys.exit(1) # glab error already on stderr; don't add a traceback
active = [p for p in pipelines if p['status'] in ('running', 'pending', 'created')]
if len(active) <= 1:
print('Nothing to cancel — at most one active pipeline.'); sys.exit(0)
print(f'Keeping newest: #{active[0][\"id\"]} ({active[0][\"status\"]})')
for p in active[1:]:
print(f'Would cancel: #{p[\"id\"]} ({p[\"status\"]})')
" | cat
glab api commands into a loop — the IDs are filled in
automatically (nothing to copy-paste), and each cancel's resulting status is
printed, so a failure is visible rather than silent:if ! CANCEL_CMDS=$(glab api "projects/$PROJECT_ID/pipelines?ref=$BRANCH&sort=desc&per_page=10" | \
python3 -c "
import json, sys
try: pipelines = json.load(sys.stdin)
except json.JSONDecodeError: sys.exit(1) # glab error already on stderr; don't add a traceback
active = [p for p in pipelines if p['status'] in ('running', 'pending', 'created')]
for p in active[1:]:
print(f'glab api -X POST projects/$PROJECT_ID/pipelines/{p[\"id\"]}/cancel')
"); then
echo "Pipeline query failed — check PROJECT_ID and glab auth (see stderr above)."
elif [ -z "$CANCEL_CMDS" ]; then
echo "Nothing to cancel — at most one active pipeline."
else
echo "$CANCEL_CMDS" | while read -r cmd; do
echo "+ $cmd"
eval "$cmd" 2>&1 | python3 -c "import json,sys; print(' ->', json.load(sys.stdin).get('status','?'))" 2>/dev/null \
|| echo " -> FAILED (cancel did not return valid JSON)"
done
fi
$PROJECT_ID inside the Python f-string is expanded by the shell before
Python runs (the python3 -c body is in a double-quoted string), so each
emitted line carries the real numeric project id — it is not a missing Python
variable. eval "$cmd" is safe here: every emitted command is a fixed
glab api -X POST …/{id}/cancel string whose only interpolated value is an
integer pipeline id straight from the API — no user-controlled or
free-text fields are evaled.
(The listing calls don't redirect 2>&1 into Python: on an API error glab
writes to stderr and Python sees empty stdin. The try/except json.JSONDecodeError: sys.exit(1) then exits cleanly — no Python traceback — so glab's own stderr
message is the sole diagnostic, and the non-zero exit drives the if ! branch
to "Pipeline query failed". The eval-loop status parse is likewise wrapped
with 2>/dev/null so a non-JSON cancel response surfaces only as FAILED.)
The preview (step 2) is the confirmation gate — eyeball it before running step 3. For a per-command y/n prompt instead, replace the loop body with
read -p "run: $cmd ? " a </dev/tty && [ "$a" = y ] && eval "$cmd".
for BRANCH in branch1 branch2; do
echo "=== $BRANCH ==="
if ! CANCEL_CMDS=$(glab api "projects/$PROJECT_ID/pipelines?ref=$BRANCH&sort=desc&per_page=10" | \
python3 -c "
import json, sys
try: pipelines = json.load(sys.stdin)
except json.JSONDecodeError: sys.exit(1) # glab error already on stderr; don't add a traceback
active = [p for p in pipelines if p['status'] in ('running', 'pending', 'created')]
for p in active[1:]:
print(f'glab api -X POST projects/$PROJECT_ID/pipelines/{p[\"id\"]}/cancel')
"); then
echo " Pipeline query failed — check PROJECT_ID and glab auth (see stderr above)."
continue
elif [ -z "$CANCEL_CMDS" ]; then
echo " Nothing to cancel — at most one active pipeline."
else
echo "$CANCEL_CMDS" | while read -r cmd; do
echo "+ $cmd"
eval "$cmd" 2>&1 | python3 -c "import json,sys; print(' ->', json.load(sys.stdin).get('status','?'))" 2>/dev/null \
|| echo " -> FAILED (cancel did not return valid JSON)"
done
fi
done
To dry-run instead, echo "$CANCEL_CMDS" (or skip the if/else and just
print the variable) — that shows the raw glab api … cancel commands without
running them. For the friendly Would cancel: #N listing, run step 2's preview
per branch.
glab api "projects?search=<repo-name>" | python3 -c "...".success or failed pipelines — they're already done.claude-review job runs early and fast; the check-package job is usually
what's hogging the runner. Canceling a superseded pipeline frees the runner for
the newer one.glab api through | cat to avoid pager issues.