Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ script-test:
$(call run-timed,bash internal/scaffold/fullsend-repo/scripts/post-triage-test.sh)
$(call run-timed,bash internal/scaffold/fullsend-repo/scripts/post-prioritize-test.sh)
$(call run-timed,bash internal/scaffold/fullsend-repo/scripts/post-code-test.sh)
$(call run-timed,bash internal/scaffold/fullsend-repo/scripts/post-fix-test.sh)
$(call run-timed,bash internal/scaffold/fullsend-repo/scripts/post-review-test.sh)
$(call run-timed,bash internal/scaffold/fullsend-repo/scripts/reconcile-repos-test.sh)
$(call run-timed,bash internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh)
Expand Down
218 changes: 218 additions & 0 deletions internal/scaffold/fullsend-repo/scripts/lib/post-failure-report.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/usr/bin/env bash
# post-failure-report.sh — Categorized, sanitized failure comments for post-scripts.
#
# Source from post-code.sh / post-fix.sh:
# source "${SCRIPT_DIR}/lib/post-failure-report.sh"
#
# Set POST_FAILURE_CATEGORY / POST_FAILURE_DETAIL before exit, or call post_fail.

# shellcheck shell=bash

[[ -n "${POST_FAILURE_REPORT_SH_LOADED:-}" ]] && return 0
POST_FAILURE_REPORT_SH_LOADED=1

POST_FAILURE_CATEGORY="${POST_FAILURE_CATEGORY:-}"
POST_FAILURE_DETAIL="${POST_FAILURE_DETAIL:-}"
POST_FAILURE_REPORTED=false

# Maximum lines of sanitized detail to include in issue/PR comments.
POST_FAILURE_DETAIL_MAX_LINES="${POST_FAILURE_DETAIL_MAX_LINES:-30}"

set_post_failure() {
POST_FAILURE_CATEGORY="$1"
POST_FAILURE_DETAIL="$2"
}

# Strip tokens and truncate noisy command output before posting publicly.
sanitize_failure_detail() {
local detail="$1"
local max_lines="${2:-${POST_FAILURE_DETAIL_MAX_LINES}}"

detail="$(printf '%s\n' "${detail}" \
| sed -E \
-e 's/ghp_[A-Za-z0-9_]+/[REDACTED]/g' \
-e 's/github_pat_[A-Za-z0-9_]+/[REDACTED]/g' \
-e 's/x-access-token:[^@[:space:]]+/x-access-token:[REDACTED]/g' \
-e 's/(Bearer|token)[[:space:]]+[A-Za-z0-9._-]+/\1 [REDACTED]/gi' \
-e 's/-----BEGIN [A-Z ]*PRIVATE KEY-----[^-]*-----END [A-Z ]*PRIVATE KEY-----/[REDACTED PRIVATE KEY]/g')"

if [ "${max_lines}" -gt 0 ]; then
detail="$(printf '%s\n' "${detail}" | tail -n "${max_lines}")"
fi

printf '%s' "${detail}"
}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

categorize_push_failure() {
local push_output="$1"

if echo "${push_output}" | grep -qiE \
Comment thread
ifireball marked this conversation as resolved.
'workflow.*without.*workflows?[[:space:]]+permission|refusing to allow.*GitHub App.*workflow'; then
echo "push-workflow-permission"
return 0
fi

if echo "${push_output}" | grep -qiE \
'non-fast-forward|rejected|fetch first|protected branch|GH006|permission denied'; then
echo "push-rejected"
return 0
fi

echo "push-rejected"
}

post_failure_category_label() {
case "$1" in
secret-scan) echo "Secret scan blocked" ;;
pre-commit-blocked) echo "Pre-commit blocked" ;;
signed-off-by) echo "Signed-off-by rejected" ;;
push-workflow-permission) echo "Push rejected — workflows permission" ;;
push-rejected) echo "Push rejected" ;;
pr-creation-failed) echo "PR creation failed" ;;
branch-validation) echo "Branch validation failed" ;;
setup-error) echo "Setup error" ;;
process-output-failed) echo "Structured output processing failed" ;;
*) echo "Post-script failed" ;;
esac
}

post_failure_environmental_note() {
case "$1" in
push-workflow-permission)
cat <<'EOF'
> **Environmental limitation:** the GitHub App lacks `workflows` write permission on this repository. The agent's patch is not necessarily wrong — update repo or app permissions (or avoid `.github/workflows/` changes) and retry.
EOF
;;
*)
printf ''
;;
esac
}

post_failure_workflow_run_url() {
local repo_full_name="$1"
local run_repo="${GITHUB_REPOSITORY:-${repo_full_name}}"
printf '%s/%s/actions/runs/%s' \
"${GITHUB_SERVER_URL:-https://github.com}" \
"${run_repo}" \
"${GITHUB_RUN_ID:-unknown}"
}

build_post_failure_comment() {
local agent_kind="$1" # code | fix
local exit_code="$2"
local category="$3"
local detail="$4"
local repo_full_name="$5"
local retry_command="$6"

local label env_note sanitized_detail run_url detail_block

label="$(post_failure_category_label "${category}")"
env_note="$(post_failure_environmental_note "${category}")"
run_url="$(post_failure_workflow_run_url "${repo_full_name}")"
sanitized_detail="$(sanitize_failure_detail "${detail}")"

if [ -n "${sanitized_detail}" ]; then
detail_block="$(cat <<EOF

**Details:**
\`\`\`
${sanitized_detail}
\`\`\`
EOF
)"
else
detail_block=""
fi

if [ -n "${env_note}" ]; then
env_note="${env_note}

"
fi

cat <<EOF
⚠️ **Post-${agent_kind} script failed** — ${label} (exit code ${exit_code})

The ${agent_kind} agent completed, but the post-${agent_kind} script failed before finishing.

${env_note}**Workflow run:** ${run_url}
${detail_block}
Please check the workflow logs for full details and retry with \`${retry_command}\` if appropriate.
EOF
}

_post_failure_ensure_token() {
if [ -z "${GH_TOKEN:-}" ]; then
export GH_TOKEN="${PUSH_TOKEN:-}"
fi
}

report_post_failure_to_issue() {
local exit_code="${1:-$?}"

if [ "${POST_FAILURE_REPORTED}" = "true" ]; then
return 0
fi
POST_FAILURE_REPORTED=true

_post_failure_ensure_token

local category="${POST_FAILURE_CATEGORY:-post-script-error}"
local detail="${POST_FAILURE_DETAIL:-Post-code script failed before push or PR creation completed.}"
local body
# ISSUE_NUMBER and REPO_FULL_NAME are required by post-code.sh before sourcing.
# shellcheck disable=SC2153
body="$(build_post_failure_comment \
"code" "${exit_code}" "${category}" "${detail}" \
"${REPO_FULL_NAME}" "/fs-code")"

echo "::warning::Posting failure comment to issue #${ISSUE_NUMBER}..."
gh issue comment "${ISSUE_NUMBER}" \
--repo "${REPO_FULL_NAME}" \
--body "${body}" 2>/dev/null || \
echo "::warning::Failed to post error comment to issue #${ISSUE_NUMBER}"
}

report_post_failure_to_pr() {
local exit_code="${1:-$?}"

if [ "${POST_FAILURE_REPORTED}" = "true" ]; then
return 0
fi
POST_FAILURE_REPORTED=true

_post_failure_ensure_token

local category="${POST_FAILURE_CATEGORY:-post-script-error}"
local detail="${POST_FAILURE_DETAIL:-Post-fix script failed before push or PR update completed.}"
local body
# PR_NUMBER and REPO_FULL_NAME are required by post-fix.sh before sourcing.
# shellcheck disable=SC2153
body="$(build_post_failure_comment \
"fix" "${exit_code}" "${category}" "${detail}" \
"${REPO_FULL_NAME}" "/fs-fix")"

echo "::warning::Posting failure comment to PR #${PR_NUMBER}..."
gh pr comment "${PR_NUMBER}" \
--repo "${REPO_FULL_NAME}" \
--body "${body}" 2>/dev/null || \
echo "::warning::Failed to post error comment to PR #${PR_NUMBER}"
}

post_fail_to_issue() {
local category="$1"
local detail="${2:-}"
set_post_failure "${category}" "${detail}"
report_post_failure_to_issue 1
exit 1
}

post_fail_to_pr() {
local category="$1"
local detail="${2:-}"
set_post_failure "${category}" "${detail}"
report_post_failure_to_pr 1
exit 1
}
Loading
Loading