Skip to content

claude.yml: don't self-trigger on bot-authored @claude comments#914

Merged
d-morrison merged 4 commits into
mainfrom
claude/focused-bohr-u9ss1u
Jun 20, 2026
Merged

claude.yml: don't self-trigger on bot-authored @claude comments#914
d-morrison merged 4 commits into
mainfrom
claude/focused-bohr-u9ss1u

Conversation

@d-morrison

@d-morrison d-morrison commented Jun 18, 2026

Copy link
Copy Markdown
Owner

What

Guard the Claude Code agent workflow (.github/workflows/claude.yml) so it only runs when a human mentions @claude — not when a bot-authored comment merely contains the string @claude.

Why

The job if: triggered on any comment/review/issue whose body contains @claude, with no guard on the author:

if: |
  (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
  ...

That let the agent re-trigger itself. A comment posted by claude[bot] (the agent's own GitHub App identity) that merely contains @claude — e.g. quoting a prior `@claude fix nits` command, or a posted by @claude signature — satisfies the if: and fires a fresh run, which posts another such comment, which fires it again: an unbounded self-trigger loop.

This was observed live on #900 on 2026-06-16: a status comment containing the literal @claude fix nits dispatched a run; that run's claude[bot] comment echoed @claude while explaining the false-positive; that re-dispatched the workflow; and so on (~12–15 min per iteration). See runs 2764641358927646992663.

Why the existing protection didn't cover it

The inline comment on the Acknowledge @claude mention step notes that the ack can't self-loop. That's true — but only because the ack is posted via GITHUB_TOKEN (as github-actions[bot]), and GITHUB_TOKEN-authored events cannot trigger workflows. claude[bot] comments, however, are posted with the Claude GitHub App token, which does fire issue_comment. So the token-kind protection never applied to the agent's own App-authored comments.

Change

Gate the job on the comment/review/issue author not being a bot:

if: |
  github.event.sender.type != 'Bot' && !endsWith(github.actor, '[bot]') && (
    (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
    (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
    (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
    (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
  )

github.event.sender is the actor that raised the event (the comment/review author or issue opener) for all four trigger types, so a single top-level guard covers them uniformly. This mirrors the full sender.type != 'Bot' && !endsWith(github.actor, '[bot]') double guard already used in claude-code-review.yml, keeping the two workflows idiomatically identical. (sender.type != 'Bot' alone is sufficient for the observed claude[bot] loop; the !endsWith(actor, '[bot]') clause is belt-and-suspenders for a bot actor whose sender.type is somehow not reported as Bot — added in response to review feedback.)

Intended human invocations (OWNER/MEMBER typing @claude) are unaffected — human actor logins never end in [bot]. If a specific automation should ever be allowed to invoke the agent, add an explicit allow-clause (the same pattern #900 proposed for letting Claude pushes through the review filter).

Verification

  • python3 parses the workflow; the jobs.claude.if expression has balanced parentheses (12/12), retains all four trigger conditions, and includes the sender.type != 'Bot' && !endsWith(github.actor, '[bot]') double guard.
  • No other job exists in the file (single claude job), so no mirrored expression to keep in sync (concurrency.cancel-in-progress here is the literal false).
  • Merged latest main (which added actions: write to this job) cleanly.

Per CLAUDE.md, this is a .github/CI-only change in its own dedicated PR (no book content touched).

🤖 Generated with Claude Code

https://claude.ai/code/session_01QGCFAvjMVUtwCpRgSFr81G

The job `if:` fired on any comment/review/issue whose body contains
`@claude`, with no guard on the author. That let the agent re-trigger
itself: a comment posted by claude[bot] (the agent's own GitHub App
identity) that merely contains `@claude` — e.g. quoting a prior
`@claude ...` command or a "posted by @claude" signature — re-fired this
workflow, which posted another such comment, which re-fired it again, in
an unbounded loop (observed on PR #900 on 2026-06-16).

The existing `Acknowledge @claude mention` ack avoids the loop only
because it is posted via GITHUB_TOKEN (github-actions[bot]), whose events
cannot trigger workflows. claude[bot] comments, however, are posted with
the Claude GitHub App token, which DOES fire `issue_comment` — so the
token-kind protection does not cover them.

Gate the job on `github.event.sender.type != 'Bot'` so only a human
@claude mention invokes the agent. `github.event.sender` is the actor
that raised the event for all four trigger types, and this mirrors the
`sender.type != 'Bot'` filter already used in claude-code-review.yml.
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude Code is working…

I'll analyze this and get back to you.

View job run

@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @d-morrison's task in 1m 39s —— View job


Code Review — PR #914

Verdict: Approve. The fix is correct, well-reasoned, and consistent with the described root cause. One minor suggestion below.


  • Read the changed file
  • Review the logic and compare with claude-code-review.yml
  • Check for edge cases

Finding: partial mirror of claude-code-review.yml's double guard

File: .github/workflows/claude.yml, line 65

github.event.sender.type != 'Bot' && (

claude-code-review.yml (line 61) uses a stricter double guard:

github.event.sender.type != 'Bot' && !endsWith(github.actor, '[bot]')

The PR description says the change "mirrors the sender.type != 'Bot' filter in claude-code-review.yml" — which is accurate as far as it goes, but it only mirrors part of that filter. The !endsWith(github.actor, '[bot]') belt-and-suspenders check is absent here.

For the specific problem described (preventing claude[bot] self-triggers), sender.type != 'Bot' is sufficient — GitHub App accounts always have type == 'Bot'. But omitting the second clause leaves a theoretical gap: a bot whose sender.type isn't properly set to 'Bot' (unusual but not impossible for older or misconfigured integrations) would slip through. Adding && !endsWith(github.actor, '[bot]') would make the two workflows idiomatically identical and close that gap with no downside.

[Fix this →](https://claude.ai/code?q=In%20.github%2Fworkflows%2Fclaude.yml%20on%20branch%20claude%2Ffocused-bohr-u9ss1u%2C%20change%20the%20job%20if%20condition%20at%20line%2065%20from%20%60github.event.sender.type%20!%3D%20'Bot'%20%26%26%20(%60%20to%20%60github.event.sender.type%20!%3D%20'Bot'%20%26%26%20!endsWith(github.actor%2C%20'%5Bbot%5D')%20%26%26%20(%60%20to%20fully%20mirror%20the%20double%20guard%20used%20in%20claude-code-review.yml%20line%2061&repo=d-morrison/rme)


Everything else looks good

  • The sender field is correctly the event actor (comment/review author or issue opener) for all four trigger types.
  • The concurrency group behavior is unaffected — skipped jobs don't hold the group slot.
  • The inline comment block is accurate and thorough; it correctly explains the token-kind vs. author-kind distinction (GITHUB_TOKEN vs. Claude App token).
  • The change is correctly isolated to a CI-only PR with no book-content changes, per CLAUDE.md.

Address review feedback on PR #914: the job `if:` guard mirrored only
the first half of claude-code-review.yml's bot filter. Add the
`!endsWith(github.actor, '[bot]')` belt-and-suspenders clause so the two
workflows use an identical double guard. This also blocks a bot actor
whose `sender.type` is somehow not reported as 'Bot' (old/misconfigured
integrations). No downside: human actor logins never end in '[bot]', so
intended human invocations are unaffected.
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @d-morrison's task in 1m 15s —— View job


Claude finished @d-morrison's task —— View job


Code Review — PR #914 (updated after commit a0c3514)

Verdict: Approve. Commit a0c3514 addresses the only finding from the previous review round. The PR is correct and ready to merge.


  • Read the changed file
  • Compare with claude-code-review.yml
  • Verify the previous finding was addressed
  • Check for new issues

Previous finding: resolved

The prior review noted that only sender.type != 'Bot' was present, missing the !endsWith(github.actor, '[bot]') belt-and-suspenders clause from claude-code-review.yml. Commit a0c3514 adds it:

github.event.sender.type != 'Bot' && !endsWith(github.actor, '[bot]') && (

This now fully mirrors line 61 of claude-code-review.yml:

(github.event.sender.type != 'Bot' && !endsWith(github.actor, '[bot]'))

The inline comment block (lines 49–66) was also updated to accurately describe both clauses and their rationale. ✓


Everything looks good

  • Parentheses balance: 11 opens / 11 closes — verified. ✓
  • All four trigger conditions retained unchanged. ✓
  • cancel-in-progress: false (a literal, not a conditional) is correct — claude.yml doesn't need the per-run cancel expression that claude-code-review.yml uses. ✓
  • The polling-loop filter inside the prompt: block (line 487: user.type is not "Bot") is a separate, unrelated construct — it doesn't need updating. ✓
  • CI-only PR with no book-content changes, per CLAUDE.md. ✓

@d-morrison d-morrison marked this pull request as ready for review June 19, 2026 00:31
@claude

claude Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @d-morrison's task in 1m 13s —— View job


Code Review — PR #914

Verdict: Approve. The fix is correct and complete. All prior findings resolved.

  • Read changed file
  • Compare with claude-code-review.yml
  • Verify prior findings resolved
  • Check for new issues

Summary

The double guard on line 68 of .github/workflows/claude.yml now reads:

github.event.sender.type != 'Bot' && !endsWith(github.actor, '[bot]') && (

This is an exact idiomatic match for the expression in claude-code-review.yml line 61:

(github.event.sender.type != 'Bot' && !endsWith(github.actor, '[bot]'))

The finding from the first review round (missing !endsWith(github.actor, '[bot]')) is resolved in commit a0c3514.

Everything checks out:

  • Parentheses are balanced across the if: block (lines 67–73).
  • All four trigger conditions (issue_comment, pull_request_review_comment, pull_request_review, issues) are retained unchanged.
  • The inline comment (lines 49–66) accurately explains the sender.type/endsWith double guard and the token-kind vs. author-kind distinction.
  • cancel-in-progress: false (a plain literal) is correct — claude.yml doesn't need the conditional cancel expression that claude-code-review.yml uses.
  • CI-only change in a dedicated PR, per CLAUDE.md.

No new issues found.

@claude

claude Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @d-morrison's task in 1m 57s —— View job


Code Review — PR #914

Verdict: Approve. The fix is correct, complete, and ready to merge.

  • Read .github/workflows/claude.yml
  • Read .github/workflows/claude-code-review.yml for comparison
  • Verify the if: guard logic
  • Check parenthesis balance
  • Check for any other issues

Summary

The only change is to the jobs.claude.if: condition (lines 67–73). The new guard:

if: |
  github.event.sender.type != 'Bot' && !endsWith(github.actor, '[bot]') && (
    (github.event_name == 'issue_comment' && ...) ||
    ...
  )

Is correct. github.event.sender is the actor that raised the event (comment/review author or issue opener) for all four trigger types, so a single top-level guard uniformly covers them. The double clause (sender.type != 'Bot' + !endsWith(actor, '[bot]')) now exactly matches the idiom used in claude-code-review.yml line 61 and concurrency.cancel-in-progress line 44.

Parentheses are balanced. Counting across lines 67–73: 11 opens, 11 closes — net 0. ✓

All four trigger conditions are retained unchanged. Only the wrapping and guard were added. ✓

cancel-in-progress: false (a plain literal, not a conditional) is correct here — claude.yml serializes runs per PR rather than cancelling them, to avoid losing in-flight work. The conditional expression in claude-code-review.yml serves a different purpose (freshest-diff-wins). ✓

The user.type is not "Bot" filter inside prompt: (line 482) is a separate in-session polling filter, not a duplicate. It prevents the agent from re-processing bot comments it encounters while polling for late-arriving @claude requests. No change needed there. ✓

The inline comment block (lines 49–66) accurately explains the sender.type/endsWith double guard, the token-kind vs. author-kind distinction, and the observed loop on PR #900. ✓

No issues found. The prior review's only finding (missing !endsWith(github.actor, '[bot]')) was resolved in commit a0c3514.

@d-morrison d-morrison merged commit aca1d7c into main Jun 20, 2026
3 checks passed
@d-morrison d-morrison deleted the claude/focused-bohr-u9ss1u branch June 20, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants