diff --git a/internal/scaffold/fullsend-repo/scripts/post-code.sh b/internal/scaffold/fullsend-repo/scripts/post-code.sh index c6e839ab18..aa05898ff6 100755 --- a/internal/scaffold/fullsend-repo/scripts/post-code.sh +++ b/internal/scaffold/fullsend-repo/scripts/post-code.sh @@ -48,7 +48,7 @@ REPO_DIR="${REPO_DIR:-repo}" if [ "${REPO_DIR}" != "." ]; then if [ ! -d "${REPO_DIR}" ]; then - echo "::error::Extracted repo not found at ${REPO_DIR}" + echo "::error::Extracted repo not found at ${REPO_DIR}" >&2 exit 1 fi cd "${REPO_DIR}" @@ -215,9 +215,9 @@ echo "Secret scan passed — no leaks in agent's commit(s)" # --------------------------------------------------------------------------- echo "Checking for Signed-off-by trailers in agent's commit(s)..." if git log --format='%b' "${SCAN_RANGE}" | grep -q '^Signed-off-by:'; then - echo "::error::BLOCKED — agent commit contains a Signed-off-by trailer" - echo "::error::Agents must not use 'git commit -s' or append Signed-off-by trailers." - echo "::error::DCO is a human attestation; the DCO app waives the check for bots." + echo "::error::BLOCKED — agent commit contains a Signed-off-by trailer" >&2 + echo "::error::Agents must not use 'git commit -s' or append Signed-off-by trailers." >&2 + echo "::error::DCO is a human attestation; the DCO app waives the check for bots." >&2 exit 1 fi echo "Signed-off-by scan passed — no trailers in agent's commit(s)" @@ -231,7 +231,7 @@ if ! command -v lychee >/dev/null 2>&1; then case "$(uname -m)" in x86_64) LY_TRIPLE="x86_64-unknown-linux-gnu"; LY_SHA="${LYCHEE_SHA256_AMD64}" ;; aarch64) LY_TRIPLE="aarch64-unknown-linux-gnu"; LY_SHA="${LYCHEE_SHA256_ARM64}" ;; - *) echo "::error::Unsupported architecture for lychee: $(uname -m)"; exit 1 ;; + *) echo "::error::Unsupported architecture for lychee: $(uname -m)" >&2; exit 1 ;; esac curl -fsSL \ "https://github.com/lycheeverse/lychee/releases/download/lychee-v${LYCHEE_VERSION}/lychee-${LY_TRIPLE}.tar.gz" \ @@ -279,9 +279,9 @@ if [ -f .pre-commit-config.yaml ]; then if pre-commit run --files "${changed_array[@]}"; then echo "Pre-commit passed — all hooks clean" else - echo "::error::BLOCKED — pre-commit hooks failed on agent's changes" - echo "::error::The agent's code does not pass the repo's pre-commit hooks." - echo "::error::Fix the issues and re-run, or update the pre-commit config." + echo "::error::BLOCKED — pre-commit hooks failed on agent's changes" >&2 + echo "::error::The agent's code does not pass the repo's pre-commit hooks." >&2 + echo "::error::Fix the issues and re-run, or update the pre-commit config." >&2 exit 1 fi else @@ -334,7 +334,8 @@ if [ "${PUSH_RC}" -ne 0 ]; then echo "::warning::Plain push failed (non-fast-forward) — retrying with --force-with-lease" git push --force-with-lease -u origin -- "${BRANCH}" 2>&1 else - echo "::error::Push failed with unexpected error" + echo "::error::Push failed with unexpected error (git push origin ${BRANCH})" >&2 + echo "::error::Push output: ${PUSH_OUTPUT}" >&2 exit 1 fi fi @@ -406,15 +407,19 @@ Closes #${ISSUE_NUMBER} - [x] Pre-commit hooks passed (authoritative run on runner) - [x] Tests ran inside sandbox" +PR_CREATE_STDERR=$(mktemp) if ! PR_URL=$(gh pr create \ --repo "${REPO_FULL_NAME}" \ --head "${BRANCH}" \ --base "${TARGET_BRANCH}" \ --title "${PR_TITLE}" \ - --body "${PR_BODY}"); then - echo "::error::Failed to create PR: see above for details" + --body "${PR_BODY}" 2>"${PR_CREATE_STDERR}"); then + echo "::error::Failed to create PR for ${REPO_FULL_NAME} (head: ${BRANCH}, base: ${TARGET_BRANCH})" >&2 + [ -s "${PR_CREATE_STDERR}" ] && cat "${PR_CREATE_STDERR}" >&2 + rm -f "${PR_CREATE_STDERR}" exit 1 fi +rm -f "${PR_CREATE_STDERR}" echo "PR created: ${PR_URL}" echo "pr_url=${PR_URL}" >> "${GITHUB_OUTPUT:-/dev/null}" diff --git a/internal/scaffold/fullsend-repo/scripts/post-fix.sh b/internal/scaffold/fullsend-repo/scripts/post-fix.sh index 5f2fe75714..84721af3af 100644 --- a/internal/scaffold/fullsend-repo/scripts/post-fix.sh +++ b/internal/scaffold/fullsend-repo/scripts/post-fix.sh @@ -73,7 +73,7 @@ RUN_DIR="$(pwd)" if [ "${REPO_DIR}" != "." ]; then if [ ! -d "${REPO_DIR}" ]; then - echo "::error::Extracted repo not found at ${REPO_DIR}" + echo "::error::Extracted repo not found at ${REPO_DIR}" >&2 exit 1 fi cd "${REPO_DIR}" @@ -172,9 +172,9 @@ if [ "${NO_PUSH}" = "false" ]; then # ------------------------------------------------------------------------- echo "Checking for Signed-off-by trailers in agent's commit(s)..." if git log --format='%b' "${SCAN_RANGE}" | grep -q '^Signed-off-by:'; then - echo "::error::BLOCKED — agent commit contains a Signed-off-by trailer" - echo "::error::Agents must not use 'git commit -s' or append Signed-off-by trailers." - echo "::error::DCO is a human attestation; the DCO app waives the check for bots." + echo "::error::BLOCKED — agent commit contains a Signed-off-by trailer" >&2 + echo "::error::Agents must not use 'git commit -s' or append Signed-off-by trailers." >&2 + echo "::error::DCO is a human attestation; the DCO app waives the check for bots." >&2 exit 1 fi echo "Signed-off-by scan passed — no trailers in agent's commit(s)" @@ -189,7 +189,7 @@ if ! command -v lychee >/dev/null 2>&1; then case "$(uname -m)" in x86_64) LY_TRIPLE="x86_64-unknown-linux-gnu"; LY_SHA="${LYCHEE_SHA256_AMD64}" ;; aarch64) LY_TRIPLE="aarch64-unknown-linux-gnu"; LY_SHA="${LYCHEE_SHA256_ARM64}" ;; - *) echo "::error::Unsupported architecture for lychee: $(uname -m)"; exit 1 ;; + *) echo "::error::Unsupported architecture for lychee: $(uname -m)" >&2; exit 1 ;; esac curl -fsSL \ "https://github.com/lycheeverse/lychee/releases/download/lychee-v${LYCHEE_VERSION}/lychee-${LY_TRIPLE}.tar.gz" \ @@ -236,7 +236,7 @@ if [ "${NO_PUSH}" = "false" ] && [ -f .pre-commit-config.yaml ]; then if pre-commit run --files "${changed_array[@]}"; then echo "Pre-commit passed — all hooks clean" else - echo "::error::BLOCKED — pre-commit hooks failed on agent's changes" + echo "::error::BLOCKED — pre-commit hooks failed on agent's changes" >&2 exit 1 fi else @@ -294,7 +294,7 @@ else SCAN_DIR="$(mktemp -d)" cp "${RESULT_FILE}" "${SCAN_DIR}/fix-result.json" if ! gitleaks detect --source "${SCAN_DIR}" --no-git --redact 2>/dev/null; then - echo "::error::Secret detected in fix-result.json — refusing to post PR comment" + echo "::error::Secret detected in fix-result.json — refusing to post PR comment" >&2 rm -rf "${SCAN_DIR}" exit 1 fi @@ -305,7 +305,8 @@ else PROCESS_EXIT=0 python3 "${PROCESS_SCRIPT}" "${RESULT_FILE}" "${REPO_FULL_NAME}" "${PR_NUMBER}" || PROCESS_EXIT=$? if [ "${PROCESS_EXIT}" -eq 1 ]; then - exit 1 # hard failure (bad input) + echo "::error::process-fix-result.py failed with exit code 1 (bad input) for PR #${PR_NUMBER} in ${REPO_FULL_NAME}" >&2 + exit 1 elif [ "${PROCESS_EXIT}" -ne 0 ]; then echo "::warning::process-fix-result.py exited ${PROCESS_EXIT} — continuing with labels/summary" fi diff --git a/internal/scaffold/fullsend-repo/scripts/post-prioritize.sh b/internal/scaffold/fullsend-repo/scripts/post-prioritize.sh index d51140573a..5c57b2914b 100755 --- a/internal/scaffold/fullsend-repo/scripts/post-prioritize.sh +++ b/internal/scaffold/fullsend-repo/scripts/post-prioritize.sh @@ -23,7 +23,7 @@ source "${SCRIPT_DIR}/lib/github-api-csma.sh" # Validate URL format early, before any parsing or API calls. if [[ ! "${GITHUB_ISSUE_URL}" =~ ^https://github\.com/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+/issues/[0-9]+$ ]]; then - echo "ERROR: GITHUB_ISSUE_URL does not match expected pattern: ${GITHUB_ISSUE_URL}" + echo "ERROR: GITHUB_ISSUE_URL does not match expected pattern: ${GITHUB_ISSUE_URL}" >&2 exit 1 fi @@ -36,14 +36,14 @@ for dir in iteration-*/output; do done if [[ -z "${RESULT_FILE}" ]]; then - echo "ERROR: agent-result.json not found in any iteration output directory" + echo "ERROR: agent-result.json not found in any iteration output directory" >&2 exit 1 fi echo "Reading RICE result from: ${RESULT_FILE}" if ! jq empty "${RESULT_FILE}" 2>/dev/null; then - echo "ERROR: ${RESULT_FILE} is not valid JSON" + echo "ERROR: ${RESULT_FILE} is not valid JSON" >&2 exit 1 fi @@ -99,7 +99,7 @@ ITEM_ID=$(echo "${ITEM_RESPONSE}" | jq -r --arg pid "${PROJECT_ID}" \ '(.data.node.projectItems.nodes // [])[] | select(.project.id == $pid) | .id') if [[ -z "${ITEM_ID}" || "${ITEM_ID}" == "null" ]]; then - echo "ERROR: issue ${GITHUB_ISSUE_URL} not found on project board" + echo "ERROR: issue ${GITHUB_ISSUE_URL} not found on project board (project: ${PROJECT_NUMBER}, org: ${ORG})" >&2 exit 1 fi @@ -118,7 +118,7 @@ SCORE_FIELD_ID=$(get_field_id "RICE Score") for fid_var in REACH_FIELD_ID IMPACT_FIELD_ID CONFIDENCE_FIELD_ID EFFORT_FIELD_ID SCORE_FIELD_ID; do if [[ -z "${!fid_var}" ]]; then - echo "ERROR: ${fid_var} not found on project board. Run scripts/setup-prioritize.sh first." + echo "ERROR: ${fid_var} not found on project board (project: ${PROJECT_NUMBER}, org: ${ORG}). Run scripts/setup-prioritize.sh first." >&2 exit 1 fi done diff --git a/internal/scaffold/fullsend-repo/scripts/post-retro.sh b/internal/scaffold/fullsend-repo/scripts/post-retro.sh index 5badca93cf..28e284bed6 100755 --- a/internal/scaffold/fullsend-repo/scripts/post-retro.sh +++ b/internal/scaffold/fullsend-repo/scripts/post-retro.sh @@ -26,7 +26,7 @@ for dir in iteration-*/output; do done if [[ -z "${RESULT_FILE}" ]]; then - echo "ERROR: agent-result.json not found in any iteration output directory" + echo "ERROR: agent-result.json not found in any iteration output directory" >&2 exit 1 fi @@ -34,14 +34,14 @@ echo "Reading retro result from: ${RESULT_FILE}" # Validate JSON is parseable. if ! jq empty "${RESULT_FILE}" 2>/dev/null; then - echo "ERROR: ${RESULT_FILE} is not valid JSON" + echo "ERROR: ${RESULT_FILE} is not valid JSON" >&2 exit 1 fi # Extract repo and number from ORIGINATING_URL. # Accepts both /issues/N and /pull/N. if [[ ! "${ORIGINATING_URL}" =~ ^https://github\.com/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+/(issues|pull)/[0-9]+$ ]]; then - echo "ERROR: ORIGINATING_URL does not match expected pattern: ${ORIGINATING_URL}" + echo "ERROR: ORIGINATING_URL does not match expected pattern: ${ORIGINATING_URL}" >&2 exit 1 fi ORIGINATING_REPO=$(echo "${ORIGINATING_URL}" | sed -E 's#https://github.com/##; s#/(issues|pull)/.*##') @@ -57,16 +57,16 @@ echo "Found ${PROPOSAL_COUNT} proposal(s)" for i in $(seq 0 $((PROPOSAL_COUNT - 1))); do TR=$(jq -r ".proposals[$i].target_repo" "${RESULT_FILE}") if [[ ! "${TR}" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then - echo "ERROR: proposal[$i].target_repo is not a valid owner/repo: ${TR}" + echo "ERROR: proposal[$i].target_repo is not a valid owner/repo: ${TR}" >&2 exit 1 fi TI=$(jq -r ".proposals[$i].title // empty" "${RESULT_FILE}") if [[ -z "${TI}" ]]; then - echo "ERROR: proposal[$i].title is missing or empty" + echo "ERROR: proposal[$i].title is missing or empty" >&2 exit 1 fi jq -e ".proposals[$i] | .what_happened and .what_could_go_better and .proposed_change and .validation_criteria" "${RESULT_FILE}" >/dev/null 2>&1 || { - echo "ERROR: proposal[$i] is missing required fields" + echo "ERROR: proposal[$i] is missing required fields" >&2 exit 1 } done @@ -98,7 +98,7 @@ for i in $(seq 0 $((PROPOSAL_COUNT - 1))); do --repo "${TARGET_REPO}" \ --title "${TITLE}" \ --body "${BODY}" 2>&1); then - echo "ERROR: failed to create issue in ${TARGET_REPO}: ${ISSUE_URL}" + echo "ERROR: failed to create issue in ${TARGET_REPO} (gh issue create --repo ${TARGET_REPO}): ${ISSUE_URL}" >&2 exit 1 fi @@ -113,7 +113,7 @@ done # number is a PR. See https://github.com/orgs/community/discussions/26644 SUMMARY=$(jq -r '.summary // empty' "${RESULT_FILE}") if [[ -z "${SUMMARY}" ]]; then - echo "ERROR: .summary is missing or empty in agent result" + echo "ERROR: .summary is missing or empty in agent result" >&2 exit 1 fi diff --git a/internal/scaffold/fullsend-repo/scripts/post-review.sh b/internal/scaffold/fullsend-repo/scripts/post-review.sh index ee196d4461..d2bdd10c7b 100755 --- a/internal/scaffold/fullsend-repo/scripts/post-review.sh +++ b/internal/scaffold/fullsend-repo/scripts/post-review.sh @@ -21,7 +21,7 @@ set -euo pipefail : "${REVIEW_TOKEN:?REVIEW_TOKEN is required}" : "${PR_NUMBER:?PR_NUMBER is required}" if ! [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then - echo "::error::PR_NUMBER must be a positive integer" + echo "::error::PR_NUMBER must be a positive integer" >&2 exit 1 fi : "${REPO_FULL_NAME:?REPO_FULL_NAME is required}" @@ -97,7 +97,7 @@ DOWNGRADED=false if [ "${ACTION}" = "approve" ]; then PR_FILES=$(gh pr view "${PR_NUMBER}" --repo "${REPO_FULL_NAME}" --json files --jq '.files[].path') if [ -z "${PR_FILES}" ]; then - echo "::error::Failed to fetch PR files or PR has no changed files — refusing to approve" + echo "::error::Failed to fetch PR files or PR has no changed files — refusing to approve (gh pr view --json files)" >&2 exit 1 fi @@ -177,6 +177,7 @@ ${REDISPATCH_MARKER}" || echo "::warning::Failed to post re-dispatch comment" # appear as a failure. exit 0 elif [ "${POST_REVIEW_EXIT}" -ne 0 ]; then + echo "::error::fullsend post-review failed with exit code ${POST_REVIEW_EXIT} (PR #${PR_NUMBER} in ${REPO_FULL_NAME})" >&2 exit "${POST_REVIEW_EXIT}" fi diff --git a/internal/scaffold/fullsend-repo/scripts/post-triage.sh b/internal/scaffold/fullsend-repo/scripts/post-triage.sh index 7077ddca13..fcfe7918b7 100755 --- a/internal/scaffold/fullsend-repo/scripts/post-triage.sh +++ b/internal/scaffold/fullsend-repo/scripts/post-triage.sh @@ -29,7 +29,7 @@ for dir in iteration-*/output; do done if [[ -z "${RESULT_FILE}" ]]; then - echo "ERROR: agent-result.json not found in any iteration output directory" + echo "ERROR: agent-result.json not found in any iteration output directory" >&2 exit 1 fi @@ -37,7 +37,7 @@ echo "Reading triage result from: ${RESULT_FILE}" # Validate JSON is parseable. if ! jq empty "${RESULT_FILE}" 2>/dev/null; then - echo "ERROR: ${RESULT_FILE} is not valid JSON" + echo "ERROR: ${RESULT_FILE} is not valid JSON" >&2 exit 1 fi @@ -47,7 +47,7 @@ COMMENT=$(jq -r '.comment // empty' "${RESULT_FILE}") # Validate and extract repo and issue number from the HTML URL. # GITHUB_ISSUE_URL is e.g. https://github.com/org/repo/issues/42 if [[ ! "${GITHUB_ISSUE_URL}" =~ ^https://github\.com/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+/issues/[0-9]+$ ]]; then - echo "ERROR: GITHUB_ISSUE_URL does not match expected pattern: ${GITHUB_ISSUE_URL}" + echo "ERROR: GITHUB_ISSUE_URL does not match expected pattern: ${GITHUB_ISSUE_URL}" >&2 exit 1 fi REPO=$(echo "${GITHUB_ISSUE_URL}" | sed 's|https://github.com/||; s|/issues/.*||') @@ -59,8 +59,11 @@ echo "Issue: #${ISSUE_NUMBER}" # add_label uses the labels API to avoid firing issues.edited. add_label() { - if ! gh api "repos/${REPO}/issues/${ISSUE_NUMBER}/labels" -f "labels[]=$1" --silent; then - echo "ERROR: failed to add label '$1' to issue #${ISSUE_NUMBER}" >&2 + local endpoint="repos/${REPO}/issues/${ISSUE_NUMBER}/labels" + local err_output + if ! err_output=$(gh api "${endpoint}" -f "labels[]=$1" --silent 2>&1); then + echo "ERROR: failed to add label '$1' to issue #${ISSUE_NUMBER} (POST ${endpoint})" >&2 + [[ -n "${err_output}" ]] && echo "ERROR: ${err_output}" >&2 exit 1 fi } @@ -98,7 +101,7 @@ DEFERRED_LABEL="" case "${ACTION}" in insufficient) if [[ -z "${COMMENT}" ]]; then - echo "ERROR: action is 'insufficient' but no comment provided" + echo "ERROR: action is 'insufficient' but no comment provided" >&2 exit 1 fi remove_label "blocked" @@ -107,12 +110,12 @@ case "${ACTION}" in duplicate) if [[ -z "${COMMENT}" ]]; then - echo "ERROR: action is 'duplicate' but no comment provided" + echo "ERROR: action is 'duplicate' but no comment provided" >&2 exit 1 fi DUPLICATE_OF=$(jq -r '.duplicate_of' "${RESULT_FILE}") if [[ "${DUPLICATE_OF}" -eq "${ISSUE_NUMBER}" ]]; then - echo "ERROR: issue cannot be a duplicate of itself (#${ISSUE_NUMBER})" + echo "ERROR: issue cannot be a duplicate of itself (#${ISSUE_NUMBER})" >&2 exit 1 fi remove_label "blocked" @@ -121,7 +124,7 @@ case "${ACTION}" in prerequisites) if [[ -z "${COMMENT}" ]]; then - echo "ERROR: action is 'prerequisites' but no comment provided" + echo "ERROR: action is 'prerequisites' but no comment provided" >&2 exit 1 fi @@ -241,7 +244,7 @@ ${FAILED_CREATES}" sufficient) if [[ -z "${COMMENT}" ]]; then - echo "ERROR: action is 'sufficient' but no comment provided" + echo "ERROR: action is 'sufficient' but no comment provided" >&2 exit 1 fi @@ -249,7 +252,7 @@ ${FAILED_CREATES}" # If the agent identified open questions, it should have used "insufficient". GAP_COUNT=$(jq '.triage_summary.information_gaps // [] | length' "${RESULT_FILE}") if [[ "${GAP_COUNT}" -gt 0 ]]; then - echo "ERROR: action is 'sufficient' but triage_summary contains ${GAP_COUNT} information_gaps — open questions must block triage" + echo "ERROR: action is 'sufficient' but triage_summary contains ${GAP_COUNT} information_gaps — open questions must block triage" >&2 exit 1 fi @@ -281,7 +284,7 @@ ${FAILED_CREATES}" question) if [[ -z "${COMMENT}" ]]; then - echo "ERROR: action is 'question' but no comment provided" + echo "ERROR: action is 'question' but no comment provided" >&2 exit 1 fi remove_label "blocked" @@ -290,7 +293,7 @@ ${FAILED_CREATES}" ;; *) - echo "ERROR: unknown action '${ACTION}' — this may be a newer action that post-triage.sh does not handle yet" + echo "ERROR: unknown action '${ACTION}' — this may be a newer action that post-triage.sh does not handle yet" >&2 exit 1 ;; esac