Skip to content

feat: isolate sticky comments by job ID to prevent workflow conflicts#940

Open
TosinAF wants to merge 4 commits intoanthropics:mainfrom
TosinAF:tosinaf/sticky-job-isolation
Open

feat: isolate sticky comments by job ID to prevent workflow conflicts#940
TosinAF wants to merge 4 commits intoanthropics:mainfrom
TosinAF:tosinaf/sticky-job-isolation

Conversation

@TosinAF
Copy link

@TosinAF TosinAF commented Feb 13, 2026

Summary

  • Isolate sticky comments per workflow job using <!-- bot: {jobId} --> HTML comment headers
  • Each workflow job (e.g. claude-code-review, claude-docs-review) automatically gets its own isolated comment via GITHUB_JOB
  • Preserve bot headers through the MCP comment server update flow (fetch current comment, extract header, re-prepend after sanitization)
  • Use regex-based comment matching with hasAnyHeader check for proper multi-bot isolation and legacy fallback
  • Paginate comment fetching to handle PRs with 30+ comments

Key design decisions

  • GITHUB_JOB for auto-isolation: No user configuration needed — each workflow job naturally gets a unique identifier
  • MCP server header preservation: Instead of modifying sanitizer.ts (security code), the MCP server fetches the current comment to extract and re-prepend the bot header after sanitization
  • <!-- bot: --> format: Aligns with Allowing multiple specialized bot in the same PR (using smart invisible tags) #780's header format for future compatibility
  • context.inputs.botId for matching: Uses the dynamic bot ID input instead of hardcoded CLAUDE_APP_BOT_ID

Files changed

File Change
action.yml Pass GITHUB_JOB env var
src/github/context.ts Add jobId to inputs
src/github/operations/comments/common.ts createCommentBody() with bot header, extractBotHeader()
src/github/operations/comments/create-initial.ts Regex matching, pagination, hasAnyHeader check
src/github/operations/comments/update-with-branch.ts Pass bot identifier
src/github/operations/comment-logic.ts Preserve bot header through updateCommentBody()
src/mcp/github-comment-server.ts Fetch current comment, extract + re-prepend header
src/github/utils/sanitizer.ts Reverted — no modifications to security code

Test plan

  • All 658 tests pass
  • TypeScript typecheck clean
  • Verified bot header format: <!-- bot: {jobId} -->
  • Verified header preservation through comment update lifecycle
  • Verified sanitizer strips bot headers (MCP server handles preservation separately)
  • Test with two concurrent workflow jobs on same PR

🤖 Generated with Claude Code

TosinAF and others added 4 commits February 13, 2026 08:23
When multiple workflows use `use_sticky_comment: true` on the same PR,
they previously co-opted each other's comments due to bot ID/name
matching. This adds a hidden `<!-- sticky-job: {jobId} -->` header
using `github.job` to isolate each workflow's sticky comment
automatically with no user configuration needed.

- Add sticky-job header to comments via createCommentBody()
- Match comments by job ID header instead of bot ID/name
- Preserve sticky-job headers through sanitizeContent() pipeline
- Preserve sticky-job headers through updateCommentBody() rebuilds
- Add pagination for comment search on busy PRs
- Fall back to bot ID/name matching when no job ID is available

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use `<!-- bot: {id} -->` header format instead of `<!-- sticky-job: -->`
- Add extractBotHeader() for cleaner header extraction
- Move header preservation to MCP server instead of modifying sanitizer
- Adopt regex-based matching with hasAnyHeader check and legacy fallback
- Use context.inputs.botId for dynamic bot ID matching
- Revert sanitizer.ts to keep security code untouched

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The bot_id default (41898282 = github-actions[bot]) doesn't match
claude[bot] (209825114) when using OIDC auth. This caused the sticky
comment matching to always fail, creating new comments each run.

Header-based matching is authoritative — if a comment has our specific
<!-- bot: {id} --> header, that's sufficient for identification. Bot ID
check is now only used for legacy comments without headers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Legacy comments without headers will naturally age out. No need
for bot ID fallback logic that was broken anyway (wrong default ID).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ashwin-ant
Copy link
Collaborator

Thanks for this! I tested this a bit and hit an issue.

With tag mode (@claude mentions on a PR) and use_sticky_comment: true, the bot header (<!-- bot: claude -->) is correctly added to the initial comment and preserved through the update lifecycle. GITHUB_JOB is correctly passed and used as the bot identifier. Non-sticky flow also still works fine. All good there.

Sticky comment reuse doesn't work for issue_comment events though. The condition on line 39 of create-initial.ts checks isPullRequestEvent(context), which only returns true when eventName === "pull_request". But @claude mentions on PRs fire as issue_comment events, so the lookup path is never reached and a new comment is always created. On my second @claude mention, a brand new comment was created instead of reusing the existing one, even though both had matching <!-- bot: claude --> headers. The fix is probably to check context.isPR instead of isPullRequestEvent(context), since context.isPR is true for issue_comment events on PRs too. (This bug also existed before this PR, so it's not a regression you introduced.)

A couple of smaller things:

  • The "i" flag on the header regex makes matching case-insensitive, but job IDs should probably match case-sensitively to avoid collisions between jobs like build and Build.
  • extractBotHeader makes the trailing newline optional (\n?), but if the original header lacks one, the concatenation in updateCommentBody produces <!-- bot: foo -->**Claude finished... with no line break.

Copy link
Collaborator

@ashwin-ant ashwin-ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comment

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