adopt
Bootstrap an existing repo for ForgeDock — triage open issues, apply labels, suggest a starter milestone, and identify first…
Automated CI failure resolution loop — diagnoses failures on a PR, applies targeted fixes, pushes, and loops until green or max attempts reached.
> /plugin marketplace add RapierCraftStudios/ForgeDock > /plugin install forgedock@forgedock
How it fires
How this command gets triggered: by you, by Claude, or both.
/fix-ciContext preview
What this command does when you run it.
Automated CI failure resolution loop — diagnoses failures on a PR, applies targeted fixes, pushes, and loops until green or max attempts reached.
description: Automated CI failure resolution loop — diagnoses failures on a PR, applies targeted fixes, pushes, and loops until green or max attempts reached. argument-hint: <PR number or URL> [--max-attempts N] [--repo owner/repo]
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios --> <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
**Input**: $ARGUMENTS
**Invoked by**: `/deploy-pr` (CI gate step) or directly by a user when a PR's CI is failing.
**Output**: Structured result — `{ pr, status, attempts, fixes_applied }` — printed to stdout and available to the calling skill.
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154. **NEVER use plan mode (EnterPlanMode).** **NEVER force-push or amend commits** — always create new commits.
<!-- FORGE:SPEC_LOADED — fix-ci.md loaded and active. Agent is bound by this spec. -->
---
Read `forge.yaml` before running any phase:
CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
if [ ! -f "$CONFIG_FILE" ]; then
echo "ERROR: forge.yaml not found. Run: npx forgedock init"
exit 1
fi
GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
GH_FLAG="-R ${GH_REPO}"
REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE")
STAGING_BRANCH=$(yq '.branches.staging // "staging"' "$CONFIG_FILE")Parse `$ARGUMENTS`:
| Argument | Variable | Default | |----------|----------|---------| | PR number or URL | `PR_NUMBER` | (required) | | `--max-attempts N` | `MAX_ATTEMPTS` | `5` | | `--repo owner/repo` | `GH_REPO` | from forge.yaml |
# Extract PR number (accept raw number or full GitHub URL)
PR_INPUT=$(echo "$ARGUMENTS" | awk '{print $(1)}')
if echo "$PR_INPUT" | grep -qE '^https?://'; then
PR_NUMBER=$(echo "$PR_INPUT" | grep -oE '[0-9]+$')
else
PR_NUMBER=$(echo "$PR_INPUT" | tr -d '#')
fi
MAX_ATTEMPTS=5
if echo "$ARGUMENTS" | grep -q -- "--max-attempts"; then
MAX_ATTEMPTS=$(echo "$ARGUMENTS" | grep -oE -- '--max-attempts [0-9]+' | awk '{print $(2)}')
fi
if [ -z "$PR_NUMBER" ]; then
echo "ERROR: PR number or URL is required."
echo "Usage: /fix-ci <PR number or URL> [--max-attempts N]"
exit 1
fi
echo "fix-ci: PR #${PR_NUMBER}, max attempts: ${MAX_ATTEMPTS}"---
PR_DATA=$(gh pr view "$PR_NUMBER" $GH_FLAG \
--json number,title,headRefName,baseRefName,state,isDraft,url,headRepository 2>/dev/null)
if [ -z "$PR_DATA" ]; then
echo "ERROR: PR #${PR_NUMBER} not found or not accessible."
exit 1
fi
PR_HEAD=$(echo "$PR_DATA" | jq -r '.headRefName')
PR_BASE=$(echo "$PR_DATA" | jq -r '.baseRefName')
PR_STATE=$(echo "$PR_DATA" | jq -r '.state')
PR_URL=$(echo "$PR_DATA" | jq -r '.url')
echo "PR #${PR_NUMBER}: ${PR_HEAD} → ${PR_BASE} (${PR_STATE})"
if [ "$PR_STATE" = "MERGED" ]; then
echo "PR #${PR_NUMBER} is already merged — nothing to fix."
echo '{"pr": '"$PR_NUMBER"', "status": "already_merged", "attempts": 0, "fixes_applied": []}'
exit 0
fi
if [ "$PR_STATE" = "CLOSED" ]; then
echo "PR #${PR_NUMBER} is closed — cannot fix CI on a closed PR."
exit 1
fiThe fix target is the branch the PR's **head** points to. CI fixes are committed directly to the PR's head branch — not to the base branch.
# Smart target detection: where do fixes land?
# - staging → main PRs: fixes go to staging (the head branch)
# - milestone/* → staging: fixes go to the milestone branch (the head branch)
# - feature/* PRs: fixes go to the feature branch (the head branch)
# In all cases: FIX_TARGET = PR_HEAD (the PR's source branch)
FIX_TARGET="$PR_HEAD"
echo "Fix target branch: ${FIX_TARGET}"
# HARD RULE: never target main directly
if [ "$FIX_TARGET" = "main" ] || [ "$FIX_TARGET" = "master" ]; then
echo "ERROR: fix-ci refuses to commit fixes directly to '${FIX_TARGET}'."
echo "This PR's head branch is a protected branch — something is wrong with the PR setup."
exit 1
fiLATEST_RUN=$(gh run list $GH_FLAG --branch "$PR_HEAD" --limit 1 \
--json databaseId,status,conclusion,name,createdAt 2>/dev/null | jq '.[0] // empty')
if [ -z "$LATEST_RUN" ]; then
echo "No CI runs found for branch ${PR_HEAD} — CI may not have triggered yet."
echo "Waiting up to 60s for CI to start..."
sleep 15
LATEST_RUN=$(gh run list $GH_FLAG --branch "$PR_HEAD" --limit 1 \
--json databaseId,status,conclusion,name,createdAt 2>/dev/null | jq '.[0] // empty')
fi
RUN_STATUS=$(echo "$LATEST_RUN" | jq -r '.status // "unknown"')
RUN_CONCLUSION=$(echo "$LATEST_RUN" | jq -r '.conclusion // "unknown"')
RUN_ID=$(echo "$LATEST_RUN" | jq -r '.databaseId // ""')
echo "Latest CI run ${RUN_ID}: status=${RUN_STATUS}, conclusion=${RUN_CONCLUSION}"
if [ "$RUN_CONCLUSION" = "success" ]; then
echo "CI is already green on PR #${PR_NUMBER} — no fixes needed."
echo '{"pr": '"$PR_NUMBER"', "status": "success", "attempts": 0, "fixes_applied": []}'
exit 0
fi---
Initialize tracking state:
ATTEMPT=0 FIXES_APPLIED="[]" LOOP_STATUS="pending" cd "$REPO_PATH" git fetch origin
Execute up to `MAX_ATTEMPTS` iterations:
while ATTEMPT < MAX_ATTEMPTS:
ATTEMPT += 1
echo "=== fix-ci: Attempt ${ATTEMPT}/${MAX_ATTEMPTS} ==="
→ Phase 1A: Wait for CI to finish
→ Phase 1B: Check CI result — if green, exit loop with success
→ Phase 1C: Diagnose failure
→ Phase 1D: Apply fix
→ Phase 1E: Format, commit, push
→ Loop back (CI re-triggers on new commit)
if loop exits without success AND ATTEMPT == MAX_ATTEMPTS:
→ Phase 2: Report max-attempts failurePoll the latest run on the
ForgeDock — autonomous AI dev pipeline for Claude Code. GitHub as agent memory: issue in, PR out.
Bootstrap an existing repo for ForgeDock — triage open issues, apply labels, suggest a starter milestone, and identify first…
Pull production analytics from GSC, Bing Webmaster, Clarity, Umami, Cloudflare, Stripe, and GA4 — generate insights and create…
Audit agent outputs from an orchestration run — timeline analysis, stall detection, active vs idle time breakdown
Trace a production issue or pipeline failure end-to-end through GitHub artifacts, then file a detailed improvement issue to the…
Autonomous deploy loop — runs until zero open issues remain. Detects pipeline state and resumes from any position. Fully…
Guided idea-to-backlog setup for a brand-new project — interview, defaults, doubts, spike doc, validated orchestrate-ready backlog