Skip to content

create asana mcp server guide #41

create asana mcp server guide

create asana mcp server guide #41

Workflow file for this run

name: Guide draft
on:
issues:
types: [labeled]
jobs:
draft:
if: github.event.label.name == 'guide:draft'
runs-on: ubuntu-latest
timeout-minutes: 180
concurrency:
group: guide-draft-issue-${{ github.event.issue.number }}
cancel-in-progress: false
permissions:
contents: write
issues: write
pull-requests: write
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
GH_TOKEN: ${{ secrets.AGENT_PAT || secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
steps:
- name: Ensure factory labels
run: |
set -euo pipefail
# Repo labels must exist before gh issue edit --add-label (missing label = hard fail).
ensure_label() {
local name="$1" color="$2" desc="$3"
if gh label list --limit 100 --json name --jq '.[].name' | grep -Fxq "$name"; then
return 0
fi
gh label create "$name" --color "$color" --description "$desc"
}
ensure_label "guide:draft" "1D76DB" "Trigger guide draft factory"
ensure_label "guide:in-progress" "FBCA04" "Guide draft factory running"
ensure_label "guide:blocked" "D73A4A" "Guide draft factory blocked"
- name: Preflight existing factory PR
id: preflight
run: |
set -euo pipefail
# Resume from an open factory PR for this issue (guide/issue-N-*) instead
# of refusing — clarifications should iterate on prior research/setup.
# If push succeeded but PR create flaked, also resume from an orphan
# remote factory branch (no open PR yet).
existing=$(gh pr list \
--state open \
--search "in:body \"#${ISSUE_NUMBER}\"" \
--json number,url,body,author,headRefName \
--jq "[.[] | select(.body | test(\"(?i)(closes|fixes|resolves)\\\\s+#${ISSUE_NUMBER}\\\\b\"))]")
count=$(echo "$existing" | jq 'length')
resume=false
resume_pr_url=""
resume_pr_number=""
resume_branch=""
refused=false
refused_pr_url=""
for i in $(seq 0 $((count - 1))); do
author=$(echo "$existing" | jq -r ".[$i].author.login")
head=$(echo "$existing" | jq -r ".[$i].headRefName")
url=$(echo "$existing" | jq -r ".[$i].url")
num=$(echo "$existing" | jq -r ".[$i].number")
if ! gh api "repos/${GH_REPO}/collaborators/${author}" --silent 2>/dev/null; then
continue
fi
if [[ "$head" == guide/issue-${ISSUE_NUMBER}-* ]]; then
resume=true
resume_pr_url="$url"
resume_pr_number="$num"
resume_branch="$head"
break
fi
# Non-factory collaborator PR still blocks (avoid stomping human work).
refused=true
refused_pr_url="$url"
break
done
if [ "$resume" != "true" ] && [ "$refused" != "true" ]; then
# matching-refs: prefix search without paginating all branches
refs_json=$(gh api "repos/${GH_REPO}/git/matching-refs/heads/guide/issue-${ISSUE_NUMBER}-" 2>/dev/null || echo "[]")
bcount=$(echo "$refs_json" | jq 'length')
if [ "$bcount" -eq 1 ]; then
resume=true
resume_branch=$(echo "$refs_json" | jq -r '.[0].ref | sub("^refs/heads/"; "")')
echo "Resuming from orphan factory branch ${resume_branch} (no open PR)"
elif [ "$bcount" -gt 1 ]; then
# Prefer the branch with the newest committer date.
best=""
best_date=""
while IFS= read -r row; do
b=$(echo "$row" | jq -r '.ref | sub("^refs/heads/"; "")')
sha=$(echo "$row" | jq -r '.object.sha')
[ -z "$b" ] || [ -z "$sha" ] && continue
date=$(gh api "repos/${GH_REPO}/git/commits/${sha}" --jq '.committer.date' 2>/dev/null || echo "")
if [ -n "$date" ] && { [ -z "$best_date" ] || [[ "$date" > "$best_date" ]]; }; then
best="$b"
best_date="$date"
fi
done < <(echo "$refs_json" | jq -c '.[]')
if [ -n "$best" ]; then
resume=true
resume_branch="$best"
echo "Resuming from newest orphan factory branch ${resume_branch} (${bcount} matches)"
fi
fi
fi
echo "refused=$refused" >> "$GITHUB_OUTPUT"
echo "refused_pr_url=$refused_pr_url" >> "$GITHUB_OUTPUT"
echo "resume=$resume" >> "$GITHUB_OUTPUT"
echo "resume_pr_url=$resume_pr_url" >> "$GITHUB_OUTPUT"
echo "resume_pr_number=$resume_pr_number" >> "$GITHUB_OUTPUT"
echo "resume_branch=$resume_branch" >> "$GITHUB_OUTPUT"
- name: Refuse non-factory PR
if: steps.preflight.outputs.refused == 'true'
env:
REFUSED_PR_URL: ${{ steps.preflight.outputs.refused_pr_url }}
run: |
gh issue edit "$ISSUE_NUMBER" --remove-label "guide:draft" || true
gh issue edit "$ISSUE_NUMBER" --add-label "guide:blocked" || true
gh issue comment "$ISSUE_NUMBER" --body "Refused to run: $REFUSED_PR_URL already targets this issue and is not a factory branch (\`guide/issue-${ISSUE_NUMBER}-*\`). Close it or finish that PR first, then re-add \`guide:draft\`."
- name: Transition labels
if: steps.preflight.outputs.refused != 'true'
run: |
gh issue edit "$ISSUE_NUMBER" --remove-label "guide:draft" || true
gh issue edit "$ISSUE_NUMBER" --remove-label "guide:blocked" || true
gh issue edit "$ISSUE_NUMBER" --add-label "guide:in-progress"
- name: Checkout main (first run)
if: steps.preflight.outputs.refused != 'true' && steps.preflight.outputs.resume != 'true'
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
token: ${{ secrets.AGENT_PAT || secrets.GITHUB_TOKEN }}
- name: Checkout factory branch (resume)
if: steps.preflight.outputs.refused != 'true' && steps.preflight.outputs.resume == 'true'
uses: actions/checkout@v4
with:
ref: ${{ steps.preflight.outputs.resume_branch }}
fetch-depth: 0
token: ${{ secrets.AGENT_PAT || secrets.GITHUB_TOKEN }}
- name: Setup Node.js
if: steps.preflight.outputs.refused != 'true'
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: pipeline/package-lock.json
- name: Install dependencies
if: steps.preflight.outputs.refused != 'true'
working-directory: pipeline
run: npm ci
- name: Distill issue intent
id: distill
if: steps.preflight.outputs.refused != 'true'
working-directory: pipeline
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
run: |
set -euo pipefail
if [ -z "${CURSOR_API_KEY:-}" ]; then
echo "CURSOR_API_KEY secret is not set" > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
fi
# Fold issue comments into distill context so clarifications on the
# ticket (not only the body) reach --notes on retry.
comments=$(gh api "repos/${GH_REPO}/issues/${ISSUE_NUMBER}/comments" \
--jq '[.[].body] | join("\n\n---\n\n")' \
2>/dev/null || echo "")
if [ -n "${comments}" ]; then
# Keep every line of this assignment indented — bare "## ..." at
# column 0 ends the YAML `run: |` block and breaks the workflow.
{
printf '%s\n\n' "${ISSUE_BODY:-}"
echo "## Issue thread (for clarifications)"
printf '%s\n' "${comments}"
} > "${RUNNER_TEMP}/issue-body-with-thread.txt"
export ISSUE_BODY="$(cat "${RUNNER_TEMP}/issue-body-with-thread.txt")"
fi
set +e
npm run resolve-issue -- --output "${RUNNER_TEMP}/resolved.json"
code=$?
set -e
if [ ! -f "${RUNNER_TEMP}/resolved.json" ]; then
echo "resolve-issue produced no resolved.json (exit ${code})" > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
fi
status=$(jq -r '.status' "${RUNNER_TEMP}/resolved.json")
if [ "$status" = "ok" ]; then
slug=$(jq -r '.slug' "${RUNNER_TEMP}/resolved.json")
provider=$(jq -r '.provider' "${RUNNER_TEMP}/resolved.json")
persona=$(jq -r '.persona // "it-admin"' "${RUNNER_TEMP}/resolved.json")
notes=$(jq -r '.notes // ""' "${RUNNER_TEMP}/resolved.json")
echo "slug=$slug" >> "$GITHUB_OUTPUT"
echo "provider=$provider" >> "$GITHUB_OUTPUT"
echo "persona=$persona" >> "$GITHUB_OUTPUT"
{
echo "notes<<EOF"
echo "$notes"
echo "EOF"
} >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "$status" = "needs_clarification" ]; then
reason=$(jq -r '.reason' "${RUNNER_TEMP}/resolved.json")
candidates=$(jq -r '(.candidates // []) | join(", ")' "${RUNNER_TEMP}/resolved.json")
{
echo "Distill needs clarification before drafting."
echo
echo "**Reason:** ${reason}"
if [ -n "$candidates" ]; then
echo
echo "**Candidates:** ${candidates}"
fi
echo
echo "Reply on this issue (or edit the body) clarifying which MCP server, then re-add \`guide:draft\`."
} > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
fi
echo "Unexpected distill status: ${status}" > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
- name: Comment resolved intent
if: steps.preflight.outputs.refused != 'true' && success()
env:
SLUG: ${{ steps.distill.outputs.slug }}
PROVIDER: ${{ steps.distill.outputs.provider }}
PERSONA: ${{ steps.distill.outputs.persona }}
NOTES: ${{ steps.distill.outputs.notes }}
RESUME: ${{ steps.preflight.outputs.resume }}
RESUME_PR_URL: ${{ steps.preflight.outputs.resume_pr_url }}
RESUME_BRANCH: ${{ steps.preflight.outputs.resume_branch }}
run: |
set -euo pipefail
body_file="${RUNNER_TEMP}/resolved-comment.md"
{
if [ "${RESUME}" = "true" ]; then
if [ -n "${RESUME_PR_URL}" ]; then
echo "Resuming on existing factory PR: ${RESUME_PR_URL}"
else
echo "Resuming on factory branch \`${RESUME_BRANCH}\` (no open PR yet — will open one after this run)."
fi
echo
echo "Resolved as \`${SLUG}\` (${PROVIDER}), persona \`${PERSONA}\`."
echo "Prior \`guides/${SLUG}/\` stays on the branch — research/draft will revise from those artifacts (lock skips when inputs match)."
else
echo "Resolved as \`${SLUG}\` (${PROVIDER}), persona \`${PERSONA}\`."
fi
if [ -n "${NOTES}" ]; then
echo
echo "Notes handed to the pipeline:"
echo
echo "> ${NOTES}"
fi
echo
echo "Starting \`draft-guide\`…"
} > "$body_file"
gh issue comment "$ISSUE_NUMBER" --body-file "$body_file"
- name: Create branch
id: branch
if: steps.preflight.outputs.refused != 'true' && success()
env:
SLUG: ${{ steps.distill.outputs.slug }}
RESUME: ${{ steps.preflight.outputs.resume }}
RESUME_BRANCH: ${{ steps.preflight.outputs.resume_branch }}
run: |
set -euo pipefail
git config user.name "guide-factory[bot]"
git config user.email "guide-factory[bot]@users.noreply.github.com"
if [ "${RESUME}" = "true" ]; then
echo "name=${RESUME_BRANCH}" >> "$GITHUB_OUTPUT"
echo "Resuming on ${RESUME_BRANCH}"
exit 0
fi
branch="guide/issue-${ISSUE_NUMBER}-${SLUG}"
echo "name=$branch" >> "$GITHUB_OUTPUT"
git checkout -b "$branch"
- name: Draft guide
id: draft
if: steps.preflight.outputs.refused != 'true' && success()
working-directory: pipeline
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
SLUG: ${{ steps.distill.outputs.slug }}
PERSONA: ${{ steps.distill.outputs.persona }}
NOTES: ${{ steps.distill.outputs.notes }}
run: |
set -euo pipefail
# --pause-on-scope: stop after research when material OQs lack Decision N
# replies in notes (exit 3 → research-only PR + Scope check).
args=(--overwrite --pause-on-scope)
if [ -n "${PERSONA}" ] && [ "${PERSONA}" != "it-admin" ]; then
args+=(--persona "${PERSONA}")
fi
if [ -n "${NOTES}" ]; then
args+=(--notes "${NOTES}")
fi
set +e
npm run draft-guide -- "${SLUG}" "${args[@]}"
code=$?
set -e
# Prefer the newest run record for this slug (written even on unconverged).
record=""
if compgen -G "${GITHUB_WORKSPACE}/retro/runs/*-${SLUG}.json" > /dev/null; then
record=$(ls -t "${GITHUB_WORKSPACE}"/retro/runs/*-"${SLUG}".json | head -n1)
echo "record=${record}" >> "$GITHUB_OUTPUT"
cp "$record" "${RUNNER_TEMP}/run-record.json"
fi
# CLI: 0 = converged, 2 = unconverged/blocked/failed, 3 = awaiting_scope,
# 1 = hard failure. Exit 2/3 still wrote files — open a PR for humans.
if [ "$code" -eq 0 ]; then
echo "outcome=converged" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "$code" -eq 3 ]; then
if [ -f "${GITHUB_WORKSPACE}/guides/${SLUG}/research.md" ]; then
echo "outcome=awaiting_scope" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "draft-guide exited 3 (awaiting_scope) but research.md is missing" > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
fi
if [ "$code" -eq 2 ]; then
if [ -d "${GITHUB_WORKSPACE}/guides/${SLUG}" ]; then
echo "outcome=unconverged" >> "$GITHUB_OUTPUT"
echo "draft-guide exited 2 (unconverged/blocked/failed). Opening a draft PR with whatever was written for human review." > "${RUNNER_TEMP}/failure_reason.txt"
exit 0
fi
echo "draft-guide exited 2 and guides/${SLUG}/ is missing" > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
fi
echo "draft-guide exited ${code} (hard failure; see workflow logs)" > "${RUNNER_TEMP}/failure_reason.txt"
exit "$code"
- name: Commit and push
id: push
if: steps.preflight.outputs.refused != 'true' && success()
env:
BRANCH: ${{ steps.branch.outputs.name }}
SLUG: ${{ steps.distill.outputs.slug }}
OUTCOME: ${{ steps.draft.outputs.outcome }}
run: |
set -euo pipefail
git add "guides/${SLUG}/" || true
# Matching run records for this slug (any timestamp prefix).
if compgen -G "retro/runs/*-${SLUG}.json" > /dev/null; then
git add retro/runs/*-"${SLUG}".json
fi
if git diff --cached --quiet; then
# Resume after a PR-create flake (or lock skip): branch already has the tip.
if git ls-remote --exit-code origin "refs/heads/${BRANCH}" >/dev/null 2>&1; then
echo "No new guide changes; keeping existing tip of ${BRANCH}"
echo "pushed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "No guide or run-record changes to commit" > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
fi
msg="Draft guide: ${SLUG} (issue #${ISSUE_NUMBER})"
if [ "${OUTCOME:-}" = "awaiting_scope" ]; then
msg="Research (awaiting scope): ${SLUG} (issue #${ISSUE_NUMBER})"
fi
git commit -m "$msg"
# force-with-lease keeps resume pushes from clobbering unexpected remote edits
git push --force-with-lease origin "$BRANCH"
echo "pushed=true" >> "$GITHUB_OUTPUT"
- name: Open or update PR
id: open_pr
if: steps.preflight.outputs.refused != 'true' && success()
env:
BRANCH: ${{ steps.branch.outputs.name }}
SLUG: ${{ steps.distill.outputs.slug }}
PROVIDER: ${{ steps.distill.outputs.provider }}
OUTCOME: ${{ steps.draft.outputs.outcome }}
RESUME: ${{ steps.preflight.outputs.resume }}
RESUME_PR_NUMBER: ${{ steps.preflight.outputs.resume_pr_number }}
RESUME_PR_URL: ${{ steps.preflight.outputs.resume_pr_url }}
run: |
set -euo pipefail
# Title stays short; status lives in draft vs ready + issue comments.
title="guide: ${PROVIDER}"
title="${title:0:256}"
# Needs a human reply (scope Decisions or unresolved blockers) → stay draft.
# Converged → ready for review.
needs_human=false
if [ "${OUTCOME}" = "awaiting_scope" ] || [ "${OUTCOME}" = "unconverged" ]; then
needs_human=true
fi
body_file="${RUNNER_TEMP}/pr-body.md"
{
echo "Closes #${ISSUE_NUMBER}"
echo
if [ "${OUTCOME}" = "awaiting_scope" ]; then
echo "Factory **research-only** pause for \`guides/${SLUG}/\` — material open questions need Decisions before draft."
echo
echo "**Pipeline status:** awaiting scope. See the issue comment **Scope check**."
echo "Answer with \`Decision N: …\`, then re-add \`guide:draft\` to continue."
echo
else
echo "Factory draft of \`guides/${SLUG}/\` via \`draft-guide\` (Cursor SDK)."
echo
fi
if [ "${OUTCOME}" = "unconverged" ]; then
echo "**Pipeline status:** unconverged (reviewers still had blockers after max rounds)."
echo "See the issue comment **Pipeline review** for unresolved blockers and open questions."
echo "Do not merge until those are settled."
echo
fi
if [ "${RESUME}" = "true" ]; then
echo "_Updated by a factory re-run (resume) — prior research/setup on this branch were reused where the lock allowed._"
echo
fi
if [ "$needs_human" = "true" ]; then
echo "This PR is a **draft** until the issue Decisions / blockers are settled."
else
echo "Pipeline **converged** — ready for human review (agents never commit; this Action did)."
fi
} > "$body_file"
if [ -f "${RUNNER_TEMP}/run-record.json" ]; then
echo >> "$body_file"
if [ "${OUTCOME}" = "awaiting_scope" ]; then
bash "${GITHUB_WORKSPACE}/.github/scripts/format-scope-check.sh" \
"${RUNNER_TEMP}/run-record.json" "" >> "$body_file"
else
bash "${GITHUB_WORKSPACE}/.github/scripts/format-pipeline-review.sh" \
"${RUNNER_TEMP}/run-record.json" "" \
"${GITHUB_WORKSPACE}/guides/${SLUG}" >> "$body_file"
fi
fi
# Retry transient GitHub GraphQL / 5xx flakes (seen on gh pr create).
is_transient() {
local err="$1"
echo "$err" | grep -qiE \
'GraphQL: Something went wrong|HTTP 50[0-9]|timed out|timeout|ECONNRESET|ECONNREFUSED|secondary rate limit|API rate limit'
}
retry_gh() {
local max=5 attempt=1 delay=10
local err_file="${RUNNER_TEMP}/gh-pr.err"
while true; do
if "$@" 2>"$err_file"; then
return 0
fi
local err
err=$(cat "$err_file")
if [ "$attempt" -ge "$max" ] || ! is_transient "$err"; then
echo "$err" >&2
return 1
fi
echo "Transient GitHub error on attempt ${attempt}/${max}; retrying in ${delay}s…" >&2
echo "$err" >&2
sleep "$delay"
attempt=$((attempt + 1))
delay=$((delay * 2))
done
}
if [ "${RESUME}" = "true" ] && [ -n "${RESUME_PR_NUMBER}" ]; then
if ! retry_gh gh pr edit "${RESUME_PR_NUMBER}" --title "$title" --body-file "$body_file"; then
{
echo "Pushed \`${BRANCH}\` but failed to update PR #${RESUME_PR_NUMBER} after retries."
echo "Re-add \`guide:draft\` to resume from that branch/PR."
} > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
fi
if [ "$needs_human" = "true" ]; then
gh pr ready "${RESUME_PR_NUMBER}" --undo || true
else
gh pr ready "${RESUME_PR_NUMBER}" || true
fi
echo "pr_url=${RESUME_PR_URL}" >> "$GITHUB_OUTPUT"
exit 0
fi
create_args=(--base main --head "$BRANCH" --title "$title" --body-file "$body_file")
if [ "$needs_human" = "true" ]; then
create_args+=(--draft)
fi
err_file="${RUNNER_TEMP}/gh-pr.err"
pr_url=""
max=5
attempt=1
delay=10
while true; do
set +e
pr_url=$(gh pr create "${create_args[@]}" 2>"$err_file")
create_code=$?
set -e
if [ "$create_code" -eq 0 ]; then
pr_url=$(printf '%s\n' "$pr_url" | tail -n1)
break
fi
err=$(cat "$err_file")
if [ "$attempt" -ge "$max" ] || ! is_transient "$err"; then
# Maybe create succeeded and only the response flaked — recover by URL.
existing_url=$(gh pr list --head "$BRANCH" --state open --json url --jq '.[0].url // empty' 2>/dev/null || true)
if [ -n "$existing_url" ]; then
echo "gh pr create failed but open PR exists for ${BRANCH}: ${existing_url}"
pr_url="$existing_url"
break
fi
echo "$err" >&2
{
echo "Guide was pushed to \`${BRANCH}\` but opening the PR failed after retries (likely a GitHub API flake)."
echo
echo "Re-add \`guide:draft\` — the next run will resume from that branch and retry PR create (without a blank-tree restart)."
} > "${RUNNER_TEMP}/failure_reason.txt"
exit 1
fi
echo "Transient GitHub error on attempt ${attempt}/${max}; retrying in ${delay}s…" >&2
echo "$err" >&2
sleep "$delay"
attempt=$((attempt + 1))
delay=$((delay * 2))
done
echo "pr_url=$pr_url" >> "$GITHUB_OUTPUT"
- name: Comment pipeline review or scope check on issue
if: steps.preflight.outputs.refused != 'true' && success()
env:
PR_URL: ${{ steps.open_pr.outputs.pr_url }}
OUTCOME: ${{ steps.draft.outputs.outcome }}
SLUG: ${{ steps.distill.outputs.slug }}
run: |
set -euo pipefail
body_file="${RUNNER_TEMP}/pipeline-review-comment.md"
if [ "${OUTCOME}" = "awaiting_scope" ]; then
if [ -f "${RUNNER_TEMP}/run-record.json" ]; then
bash "${GITHUB_WORKSPACE}/.github/scripts/format-scope-check.sh" \
"${RUNNER_TEMP}/run-record.json" "${PR_URL}" > "$body_file"
else
{
echo "## Scope check"
echo
echo "Research paused before draft (awaiting scope). Draft PR: ${PR_URL}"
echo
echo "_No run record found to list Decisions._"
} > "$body_file"
fi
gh issue comment "$ISSUE_NUMBER" --body-file "$body_file"
gh issue edit "$ISSUE_NUMBER" --add-label "guide:blocked" || true
exit 0
fi
if [ -f "${RUNNER_TEMP}/run-record.json" ]; then
bash "${GITHUB_WORKSPACE}/.github/scripts/format-pipeline-review.sh" \
"${RUNNER_TEMP}/run-record.json" "${PR_URL}" \
"${GITHUB_WORKSPACE}/guides/${SLUG}" > "$body_file"
else
{
echo "## Pipeline review"
echo
if [ "${OUTCOME}" = "unconverged" ]; then
echo "Draft PR opened (pipeline **unconverged**): ${PR_URL}"
else
echo "Draft PR opened: ${PR_URL}"
fi
echo
echo "_No run record found to summarize blockers / open questions._"
} > "$body_file"
fi
gh issue comment "$ISSUE_NUMBER" --body-file "$body_file"
- name: Mark blocked on failure
if: steps.preflight.outputs.refused != 'true' && failure()
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
SLUG: ${{ steps.distill.outputs.slug }}
PUSHED: ${{ steps.push.outputs.pushed }}
BRANCH: ${{ steps.branch.outputs.name }}
run: |
reason="(no reason file written; check workflow logs)"
if [ -f "${RUNNER_TEMP}/failure_reason.txt" ]; then
reason=$(cat "${RUNNER_TEMP}/failure_reason.txt")
fi
body_file="${RUNNER_TEMP}/failure-comment.md"
{
if [ "${PUSHED}" = "true" ]; then
echo "\`guide:draft\` run failed after pushing \`${BRANCH:-guide/issue-${ISSUE_NUMBER}-…}\`."
echo
echo "$reason"
echo
echo "The guide branch is on the remote — re-add \`guide:draft\` to resume from it (skipping a blank-tree restart)."
else
echo "\`guide:draft\` run failed (no PR opened)."
echo
echo "$reason"
fi
echo
echo "**Workflow run:** ${RUN_URL}"
echo
} > "$body_file"
# If a run record exists from a partial draft, surface blockers here too.
if [ -f "${RUNNER_TEMP}/run-record.json" ]; then
echo >> "$body_file"
bash "${GITHUB_WORKSPACE}/.github/scripts/format-pipeline-review.sh" \
"${RUNNER_TEMP}/run-record.json" "" \
"${GITHUB_WORKSPACE}/guides/${SLUG}" >> "$body_file" || true
elif [ -n "${SLUG}" ] && compgen -G "${GITHUB_WORKSPACE}/retro/runs/*-${SLUG}.json" > /dev/null; then
record=$(ls -t "${GITHUB_WORKSPACE}"/retro/runs/*-"${SLUG}".json | head -n1)
echo >> "$body_file"
bash "${GITHUB_WORKSPACE}/.github/scripts/format-pipeline-review.sh" \
"$record" "" "${GITHUB_WORKSPACE}/guides/${SLUG}" >> "$body_file" || true
else
{
echo "Reply on this issue with clarifications, then re-add \`guide:draft\`."
} >> "$body_file"
fi
gh issue edit "$ISSUE_NUMBER" --add-label "guide:blocked" || true
gh issue comment "$ISSUE_NUMBER" --body-file "$body_file"
- name: Always remove in-progress
if: always() && steps.preflight.outputs.refused != 'true'
run: gh issue edit "$ISSUE_NUMBER" --remove-label "guide:in-progress" || true