Skip to content
Open
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion src/github/operations/comments/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ export function createBranchLink(
export function createCommentBody(
jobRunLink: string,
branchLink: string = "",
botName: string = "",
): string {
return `Claude Code is working… ${SPINNER_HTML}
const header = botName ? `<!-- bot: ${botName} -->\n` : "";
return `${header}Claude Code is working… ${SPINNER_HTML}

I'll analyze this and get back to you.

Expand Down
30 changes: 25 additions & 5 deletions src/github/operations/comments/create-initial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ import {
} from "../../context";
import type { Octokit } from "@octokit/rest";

const CLAUDE_APP_BOT_ID = 209825114;

export async function createInitialComment(
octokit: Octokit,
context: ParsedGitHubContext,
) {
const { owner, repo } = context.repository;

const jobRunLink = createJobRunLink(owner, repo, context.runId);
const initialBody = createCommentBody(jobRunLink);
// Add hidden header with bot name for sticky comment identification
const initialBody = createCommentBody(
jobRunLink,
"",
context.inputs.useStickyComment ? context.inputs.botName : "",
);

try {
let response;
Expand All @@ -39,12 +42,29 @@ export async function createInitialComment(
issue_number: context.entityNumber,
});
const existingComment = comments.data.find((comment) => {
const idMatch = comment.user?.id === CLAUDE_APP_BOT_ID;
const idMatch = comment.user?.id === Number(context.inputs.botId);

// Check for hidden header match to support multiple bots
const hiddenHeader = `<!-- bot: ${context.inputs.botName} -->`;
const headerMatch = comment.body?.includes(hiddenHeader);

// Check if comment has ANY hidden header (to detect other bots)
const hasAnyHeader = comment.body?.includes("<!-- bot:");

const botNameMatch =
comment.user?.type === "Bot" &&
comment.user?.login.toLowerCase().includes("claude");
comment.user?.login
.toLowerCase()
.includes(context.inputs.botName.toLowerCase());
const bodyMatch = comment.body === initialBody;

// If comment has a hidden header, ONLY match if it's OUR header
// This prevents bots with the same ID but different names from conflicting
if (hasAnyHeader) {
return headerMatch && idMatch;
}

// No header present: Match by ID OR bot name OR body (backward compatibility)
return idMatch || botNameMatch || bodyMatch;
});
if (existingComment) {
Expand Down
4 changes: 3 additions & 1 deletion src/github/operations/comments/update-with-branch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export async function updateTrackingComment(
branchLink = createBranchLink(owner, repo, branch);
}

const updatedBody = createCommentBody(jobRunLink, branchLink);
// Preserve hidden header for sticky comment identification
const botName = context.inputs.useStickyComment ? context.inputs.botName : "";
const updatedBody = createCommentBody(jobRunLink, branchLink, botName);

// Update the existing comment with the branch link
try {
Expand Down
22 changes: 22 additions & 0 deletions test/sticky-comment-header.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, test } from "bun:test";
import { createCommentBody } from "../src/github/operations/comments/common";

describe("Sticky Comment Header Logic", () => {
test("generates hidden header when botName is provided", () => {
const body = createCommentBody("http://example.com", "", "claude-security");
expect(body).toContain("<!-- bot: claude-security -->");
expect(body).toContain("Claude Code is working");
});

test("does not generate header when botName is missing", () => {
const body = createCommentBody("http://example.com");
expect(body).not.toContain("<!-- bot:");
expect(body).toContain("Claude Code is working");
});

test("generates distinct bodies for different bot names", () => {
const body1 = createCommentBody("link", "", "bot1");
const body2 = createCommentBody("link", "", "bot2");
expect(body1).not.toEqual(body2);
});
});