From 5272588b1dc448cd6c2f2e74c361e2ef25e72e4f Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Tue, 7 Jul 2026 01:08:19 -0700 Subject: [PATCH] fix(agent): redact Linear tracker names in PR comments --- src/integrations/project-tracker-adapter.ts | 18 +++++++++++------- test/unit/linear-adapter.test.ts | 13 ++++++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/integrations/project-tracker-adapter.ts b/src/integrations/project-tracker-adapter.ts index c332916fd6..2128094cb6 100644 --- a/src/integrations/project-tracker-adapter.ts +++ b/src/integrations/project-tracker-adapter.ts @@ -302,17 +302,19 @@ type ProjectTrackerMatches = { project: ProjectTrackerMatch | null; }; -function describeMatch(match: ProjectTrackerMatch, noun: "milestone" | "project"): string { +function describeMatch(match: ProjectTrackerMatch, noun: "milestone" | "project", revealTitle: boolean): string { + const title = revealTitle ? ` ${codeFormat(match.item.title)}` : ""; if (match.source === "native") { - return `This PR is linked to the ${codeFormat(match.item.title)} ${noun} (confirmed via Linear's GitHub integration).`; + return `This PR is linked to the${title} ${noun} (confirmed via Linear's GitHub integration).`; } - return `This PR looks like it's part of the ${codeFormat(match.item.title)} ${noun} (${Math.round(match.score * 100)}% title/body term overlap).`; + const confidence = revealTitle ? ` (${Math.round(match.score * 100)}% title/body term overlap)` : ""; + return `This PR looks like it's part of a matching${title} ${noun}${confidence}.`; } -function renderSuggestionComment(matches: ProjectTrackerMatches): string { +function renderSuggestionComment(matches: ProjectTrackerMatches, revealTitles: boolean): string { const lines = [PROJECT_TRACKER_SUGGEST_COMMENT_MARKER]; - if (matches.milestone) lines.push(describeMatch(matches.milestone, "milestone")); - if (matches.project) lines.push(describeMatch(matches.project, "project")); + if (matches.milestone) lines.push(describeMatch(matches.milestone, "milestone", revealTitles)); + if (matches.project) lines.push(describeMatch(matches.project, "project", revealTitles)); lines.push("", "This is an advisory suggestion only — nothing has been attached automatically."); return lines.join("\n"); } @@ -397,7 +399,9 @@ export async function maybeSuggestProjectOrMilestoneMatch( } if (alreadyPosted) return { suggested: false }; - await createIssueComment(ctx.env, ctx.installationId, ctx.repoFullName, pullNumber, renderSuggestionComment(matches)); + // Linear API keys are workspace-scoped, so project/milestone names may be internal even when the GitHub + // repository is public. Keep the public suggestion useful without echoing Linear tracker titles (#3290). + await createIssueComment(ctx.env, ctx.installationId, ctx.repoFullName, pullNumber, renderSuggestionComment(matches, backend !== "linear")); return { suggested: true }; } diff --git a/test/unit/linear-adapter.test.ts b/test/unit/linear-adapter.test.ts index 6420744f85..410e7be32c 100644 --- a/test/unit/linear-adapter.test.ts +++ b/test/unit/linear-adapter.test.ts @@ -181,7 +181,7 @@ describe("maybeSuggestProjectOrMilestoneMatch with backend: linear (#3186)", () if (url === "https://api.linear.app/graphql") { const body = JSON.parse(String(init?.body ?? "{}")) as { query: string }; if (body.query.includes("attachmentsForURL")) { - return Response.json({ data: { attachmentsForURL: { nodes: [{ issue: { project: { id: "proj-1", name: "Self-host reliability roadmap" }, projectMilestone: null } }] } } }); + return Response.json({ data: { attachmentsForURL: { nodes: [{ issue: { project: { id: "proj-1", name: "Self-host reliability roadmap" }, projectMilestone: { id: "mile-1", name: "Stealth Launch M3" } } }] } } }); } projectsListed = true; return Response.json({ data: { projects: { nodes: [], pageInfo: { hasNextPage: false, endCursor: null } } } }); @@ -204,8 +204,10 @@ describe("maybeSuggestProjectOrMilestoneMatch with backend: linear (#3186)", () ); expect(result).toEqual({ suggested: true }); expect(projectsListed).toBe(false); - expect(posted[0]).toContain("linked to"); - expect(posted[0]).toContain("Self-host reliability roadmap"); + expect(posted[0]).toContain("linked to the project"); + expect(posted[0]).toContain("linked to the milestone"); + expect(posted[0]).not.toContain("Self-host reliability roadmap"); + expect(posted[0]).not.toContain("Stealth Launch M3"); expect(posted[0]).not.toContain("term overlap"); }); @@ -239,8 +241,9 @@ describe("maybeSuggestProjectOrMilestoneMatch with backend: linear (#3186)", () PR_URL, ); expect(result).toEqual({ suggested: true }); - expect(posted[0]).toContain("term overlap"); - expect(posted[0]).toContain("Self-host reliability roadmap"); + expect(posted[0]).toContain("matching project"); + expect(posted[0]).not.toContain("term overlap"); + expect(posted[0]).not.toContain("Self-host reliability roadmap"); }); it("API-error best-effort path: a Linear outage propagates to the caller instead of silently mismatching", async () => {