Skip to content
Merged
Changes from all commits
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
38 changes: 31 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ runs:

echo "credential_count=${COUNT}" >>"$GITHUB_OUTPUT"

# --- Progress comment --- A plain, ordinary issue comment posted and later edited entirely by this action's own steps via `gh api`, independent of track_progress/use_sticky_comment above: those are pure passthroughs to upstream's own tracking-comment mechanism, which only its tag mode implements, and every mode this action runs (review, triage, interactive) always sets `prompt` (see "Compose prompt" below) -- upstream's detectMode treats a non-empty prompt as agent mode, which has no tracking-comment support at all. Placed first, before stack detection or prompt composition, so the comment lands as early as possible rather than waiting on work this comment doesn't depend on.
# --- Progress comment --- A plain, ordinary issue comment posted and later deleted (on success) or edited (on failure) entirely by this action's own steps via `gh api`, independent of track_progress/use_sticky_comment above: those are pure passthroughs to upstream's own tracking-comment mechanism, which only its tag mode implements, and every mode this action runs (review, triage, interactive) always sets `prompt` (see "Compose prompt" below) -- upstream's detectMode treats a non-empty prompt as agent mode, which has no tracking-comment support at all. Placed first, before stack detection or prompt composition, so the comment lands as early as possible rather than waiting on work this comment doesn't depend on.
- name: Post progress comment
id: progress-comment
if: ${{ inputs.post_progress_comment == 'true' }}
Expand Down Expand Up @@ -598,10 +598,20 @@ runs:
*) VERB="working on this" ;;
esac

# Delete every terminal-FAILED progress comment this bot left behind on a now-finished previous run, then always create a fresh comment of this run's own -- never adopt an existing one's identity by editing it in place. Adopting via lookup-then-patch is a check-then-act race: interactive mode has no concurrency group, so two overlapping runs (or a stale failed comment plus a fresh run starting moments apart) can both find the same candidate before either has claimed it, both patch it, and both believe they own it -- whichever finishes last then loses its own outcome to a 404 when the comment underneath it has already been deleted or reused by the other. Deleting has no such race: a delete that loses to a sibling's own delete of the same comment just 404s, a harmless no-op, and creating always mints a comment only this run's own id will ever point at, so its own eventual delete-on-success or patch-to-failed below can never step on another run's.
MARKER_FAILED="<!-- claude-progress-comment:failed -->"
Comment thread
claude[bot] marked this conversation as resolved.
JQ_STALE_FILTER='map(select(.user.type == "Bot" and (.body | contains("'"$MARKER_FAILED"'")))) | .[].id'
STALE_IDS=$(gh api "repos/${REPOSITORY}/issues/${ENTITY_NUMBER}/comments" --paginate -q "$JQ_STALE_FILTER" 2>/dev/null || echo "")
for id in $STALE_IDS; do
gh api -X DELETE "repos/${REPOSITORY}/issues/comments/${id}" >/dev/null 2>&1 || true
done

BODY=$(printf '🔄 Claude is %s… [View job run](%s)' "$VERB" "$RUN_URL")

# Three attempts with a short backoff before falling back on continue-on-error above -- a transient rate limit or network blip shouldn't cost the comment even though a sustained failure (bad permissions, a real outage) still must not fail the job.
COMMENT_ID=""
for attempt in 1 2 3; do
if COMMENT_ID=$(gh api "repos/${REPOSITORY}/issues/${ENTITY_NUMBER}/comments" -f body="🔄 Claude is ${VERB}… [View job run](${RUN_URL})" -q .id); then
if COMMENT_ID=$(gh api "repos/${REPOSITORY}/issues/${ENTITY_NUMBER}/comments" -f body="$BODY" -q .id); then
break
fi
COMMENT_ID=""
Expand All @@ -613,6 +623,8 @@ runs:
exit 1
fi
echo "comment_id=${COMMENT_ID}" >> "$GITHUB_OUTPUT"
# Threaded through to "Update progress comment" below as the single source of truth for the marker text, rather than a second hardcoded literal that has to be kept in sync by hand.
echo "marker_failed=${MARKER_FAILED}" >> "$GITHUB_OUTPUT"
echo "Posted progress comment ${COMMENT_ID}."

- name: Detect stack
Expand Down Expand Up @@ -1621,7 +1633,7 @@ runs:
path: ~/.claude/projects
key: ${{ steps.cache-scope.outputs.cache_key }}

# Counterpart to "Post progress comment" above -- edits the SAME comment (by its captured id) once the whole run has finished, including any optional wrap-up/fix/summary pass above, so the placeholder never sits stale as "in progress" once real output exists. if: always() so it still fires when Run Claude Code itself failed outright -- exactly when a plain visible status marker matters most, since nothing else in this composite action guarantees any comment lands when a mode's own model-driven output (a review, a triage comment, an interactive reply) never got produced.
# Counterpart to "Post progress comment" above -- resolves the SAME comment (by its captured id) once the whole run has finished, including any optional wrap-up/fix/summary pass above. if: always() so it still fires when Run Claude Code itself failed outright -- exactly when a plain visible status marker matters most, since nothing else in this composite action guarantees any comment lands when a mode's own model-driven output (a review, a triage comment, an interactive reply) never got produced.
- name: Update progress comment
if: ${{ always() && inputs.post_progress_comment == 'true' && steps.progress-comment.outputs.comment_id != '' }}
# Same reasoning as "Post progress comment" above: this is the composite action's own final step, and a transient gh api failure here must not surface as this whole action having failed when the actual mode work (review/triage/interactive) already completed beforehand.
Expand All @@ -1631,6 +1643,7 @@ runs:
GH_TOKEN: ${{ inputs.github_token || github.token }}
REPOSITORY: ${{ github.repository }}
COMMENT_ID: ${{ steps.progress-comment.outputs.comment_id }}
MARKER_FAILED: ${{ steps.progress-comment.outputs.marker_failed }}
CLAUDE_OUTCOME: ${{ steps.claude.outcome }}
# "Wrap up on turn limit" is the one later step with no continue-on-error and no prior id (unlike the fix pass / structured-summary steps, which are all continue-on-error: true and so can never make an already-successful primary run look failed here) -- if it runs and itself fails, the job outcome is failure even though steps.claude.outcome is still the earlier, successful "success". Folded in below rather than trusted on its own, since it reports "skipped" (not "success") whenever detect-turn-limit didn't fire, which must not be read as a failure.
WRAP_UP_OUTCOME: ${{ steps.wrap-up.outcome }}
Expand All @@ -1641,12 +1654,23 @@ runs:
if [ "$OUTCOME" = "success" ] && [ -n "${WRAP_UP_OUTCOME:-}" ] && [ "$WRAP_UP_OUTCOME" != "success" ] && [ "$WRAP_UP_OUTCOME" != "skipped" ]; then
OUTCOME="$WRAP_UP_OUTCOME"
fi

# Real output now exists elsewhere (a submitted review, a triage comment, an interactive reply -- every mode always leaves one on success, per each mode's own prompt), so this placeholder has nothing left to say. Deleting it outright, rather than editing it to a "finished" message, is what stops repeat runs on the same pull request/issue from leaving a trail of near-identical status pings behind.
if [ "$OUTCOME" = "success" ]; then
BODY="✅ Claude finished — see below for the result. [View job run](${RUN_URL})"
else
BODY="❌ Claude's run did not complete (outcome: ${OUTCOME}). [View job run](${RUN_URL})"
for attempt in 1 2 3; do
if gh api -X DELETE "repos/${REPOSITORY}/issues/comments/${COMMENT_ID}"; then
echo "Deleted progress comment ${COMMENT_ID} (outcome: success)."
exit 0
fi
echo "Failed to delete progress comment (attempt ${attempt}/3)."
[ "$attempt" -lt 3 ] && sleep 2
done
echo "Giving up on deleting the progress comment after 3 attempts."
exit 1
fi
# Same three-attempt backoff as "Post progress comment" above.

# Non-success: no other durable output is guaranteed to exist, so this comment stays as the one visible signal something went wrong -- left in place, now carrying the FAILED marker (threaded through from "Post progress comment" above as the single source of truth for its exact text) so a later run's own "Post progress comment" step deletes it during its own cleanup pass instead of it piling up indefinitely.
BODY=$(printf '❌ Claude'"'"'s run did not complete (outcome: %s). [View job run](%s)\n\n%s' "$OUTCOME" "$RUN_URL" "$MARKER_FAILED")
for attempt in 1 2 3; do
if gh api -X PATCH "repos/${REPOSITORY}/issues/comments/${COMMENT_ID}" -f body="$BODY"; then
echo "Updated progress comment ${COMMENT_ID} (outcome: ${OUTCOME})."
Expand Down
Loading