Skip to content

Commit 8e16fd8

Browse files
authored
Use XML comment markers for agentic workflow detection (#13309)
1 parent a556ff7 commit 8e16fd8

File tree

2 files changed

+148
-17
lines changed

2 files changed

+148
-17
lines changed

actions/setup/js/expired_entity_search_helpers.cjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,13 @@ async function searchEntitiesWithExpiration(github, owner, repo, config) {
9797

9898
// Filter for entities with agentic workflow markers and expiration comments
9999
for (const entity of nodes) {
100-
// Check if created by an agentic workflow (body contains "> AI generated by" at start of line)
101-
const agenticPattern = /^> AI generated by/m;
102-
const isAgenticWorkflow = entity.body && agenticPattern.test(entity.body);
100+
// Check if created by an agentic workflow using XML comment markers
101+
// Look for either:
102+
// 1. <!-- gh-aw-workflow-id: ... --> (standalone workflow ID marker)
103+
// 2. <!-- gh-aw-agentic-workflow: ... --> (full metadata marker)
104+
const hasWorkflowId = entity.body && entity.body.includes("gh-aw-workflow-id:");
105+
const hasAgenticWorkflow = entity.body && entity.body.includes("gh-aw-agentic-workflow:");
106+
const isAgenticWorkflow = hasWorkflowId || hasAgenticWorkflow;
103107

104108
if (isAgenticWorkflow) {
105109
agenticCount++;

actions/setup/js/expired_entity_search_helpers.test.cjs

Lines changed: 141 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe("searchEntitiesWithExpiration", () => {
2626
number: 123,
2727
title: "Test Issue",
2828
url: "https://github.com/test-owner/test-repo/issues/123",
29-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
29+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
3030
createdAt: "2026-01-01T00:00:00Z",
3131
};
3232

@@ -59,7 +59,7 @@ describe("searchEntitiesWithExpiration", () => {
5959
number: 456,
6060
title: "Test PR",
6161
url: "https://github.com/test-owner/test-repo/pull/456",
62-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
62+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
6363
createdAt: "2026-01-01T00:00:00Z",
6464
};
6565

@@ -92,7 +92,7 @@ describe("searchEntitiesWithExpiration", () => {
9292
number: 789,
9393
title: "Test Discussion",
9494
url: "https://github.com/test-owner/test-repo/discussions/789",
95-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
95+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
9696
createdAt: "2026-01-01T00:00:00Z",
9797
};
9898

@@ -125,7 +125,7 @@ describe("searchEntitiesWithExpiration", () => {
125125
number: 1,
126126
title: "Agentic Issue",
127127
url: "https://github.com/test-owner/test-repo/issues/1",
128-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
128+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
129129
createdAt: "2026-01-01T00:00:00Z",
130130
};
131131

@@ -166,7 +166,7 @@ describe("searchEntitiesWithExpiration", () => {
166166
number: 1,
167167
title: "Issue with expiration",
168168
url: "https://github.com/test-owner/test-repo/issues/1",
169-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
169+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
170170
createdAt: "2026-01-01T00:00:00Z",
171171
};
172172

@@ -175,7 +175,7 @@ describe("searchEntitiesWithExpiration", () => {
175175
number: 2,
176176
title: "Issue without expiration",
177177
url: "https://github.com/test-owner/test-repo/issues/2",
178-
body: "> AI generated by workflow\n\nNo expiration marker",
178+
body: "> AI generated by workflow\n\nNo expiration marker\n\n<!-- gh-aw-workflow-id: test-workflow -->",
179179
createdAt: "2026-01-01T00:00:00Z",
180180
};
181181

@@ -207,7 +207,7 @@ describe("searchEntitiesWithExpiration", () => {
207207
number: 12667,
208208
title: "Legacy Format Issue",
209209
url: "https://github.com/test-owner/test-repo/issues/12667",
210-
body: "> AI generated by workflow\n\n> - [x] expires on Jan 31, 2026, 6:04 AM UTC",
210+
body: "> AI generated by workflow\n\n> - [x] expires on Jan 31, 2026, 6:04 AM UTC\n\n<!-- gh-aw-workflow-id: test-workflow -->",
211211
createdAt: "2026-01-30T04:04:42Z",
212212
};
213213

@@ -216,7 +216,7 @@ describe("searchEntitiesWithExpiration", () => {
216216
number: 123,
217217
title: "New Format Issue",
218218
url: "https://github.com/test-owner/test-repo/issues/123",
219-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z --> on Dec 31, 2026, 11:59 PM UTC",
219+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z --> on Dec 31, 2026, 11:59 PM UTC\n\n<!-- gh-aw-workflow-id: test-workflow -->",
220220
createdAt: "2026-01-01T00:00:00Z",
221221
};
222222

@@ -249,7 +249,7 @@ describe("searchEntitiesWithExpiration", () => {
249249
number: 1,
250250
title: "Issue 1",
251251
url: "https://github.com/test-owner/test-repo/issues/1",
252-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
252+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
253253
createdAt: "2026-01-01T00:00:00Z",
254254
};
255255

@@ -258,7 +258,7 @@ describe("searchEntitiesWithExpiration", () => {
258258
number: 2,
259259
title: "Issue 2",
260260
url: "https://github.com/test-owner/test-repo/issues/2",
261-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
261+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
262262
createdAt: "2026-01-01T00:00:00Z",
263263
};
264264

@@ -301,7 +301,7 @@ describe("searchEntitiesWithExpiration", () => {
301301
number: 1,
302302
title: "Duplicate Discussion",
303303
url: "https://github.com/test-owner/test-repo/discussions/1",
304-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
304+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
305305
createdAt: "2026-01-01T00:00:00Z",
306306
};
307307

@@ -346,7 +346,7 @@ describe("searchEntitiesWithExpiration", () => {
346346
number: 1,
347347
title: "Issue 1",
348348
url: "https://github.com/test-owner/test-repo/issues/1",
349-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
349+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
350350
createdAt: "2026-01-01T00:00:00Z",
351351
};
352352

@@ -355,7 +355,7 @@ describe("searchEntitiesWithExpiration", () => {
355355
number: 1,
356356
title: "Issue 1",
357357
url: "https://github.com/test-owner/test-repo/issues/1",
358-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
358+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
359359
createdAt: "2026-01-01T00:00:00Z",
360360
};
361361

@@ -441,7 +441,7 @@ describe("searchEntitiesWithExpiration", () => {
441441
number: 123,
442442
title: "Test Issue",
443443
url: "https://github.com/test-owner/test-repo/issues/123",
444-
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
444+
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
445445
createdAt: "2026-01-01T00:00:00Z",
446446
};
447447

@@ -468,4 +468,131 @@ describe("searchEntitiesWithExpiration", () => {
468468
expect(global.core.info).toHaveBeenCalledWith(expect.stringMatching(/Found issue #123 with expiration marker \((new|legacy) format\):/));
469469
expect(global.core.info).toHaveBeenCalledWith("Search complete: Scanned 1 issues across 1 pages, found 1 with expiration markers");
470470
});
471+
472+
it("should NOT find issue #12669 without XML workflow markers", async () => {
473+
// Issue #12669: https://github.com/githubnext/gh-aw/issues/12669
474+
// This issue was not detected because it lacks XML comment markers
475+
// (gh-aw-workflow-id or gh-aw-agentic-workflow)
476+
// This is correct behavior - only issues with XML markers should be auto-closed
477+
const issue12669 = {
478+
id: "I_kwDOPc1QR87m5TZ3",
479+
number: 12669,
480+
title: "Smoke Copilot - Issue Group",
481+
url: "https://github.com/githubnext/gh-aw/issues/12669",
482+
body: `# Smoke Copilot
483+
484+
Parent issue for grouping related issues from <a>Smoke Copilot</a>.
485+
486+
487+
488+
Sub-issues are automatically linked below (max 64 per parent).
489+
490+
491+
> Workflow: [Smoke Copilot]()
492+
> - [x] expires on Jan 31, 2026, 6:05 AM UTC`,
493+
createdAt: "2026-01-30T04:05:02Z",
494+
};
495+
496+
mockGithub = {
497+
graphql: vi.fn().mockResolvedValue({
498+
repository: {
499+
issues: {
500+
pageInfo: { hasNextPage: false, endCursor: null },
501+
nodes: [issue12669],
502+
},
503+
},
504+
}),
505+
};
506+
507+
const result = await searchEntitiesWithExpiration(mockGithub, owner, repo, {
508+
entityType: "issues",
509+
graphqlField: "issues",
510+
resultKey: "issues",
511+
});
512+
513+
// This issue should NOT be found because it lacks XML workflow markers
514+
// Issues without markers are not managed by agentic workflows and shouldn't be auto-closed
515+
expect(result.items).toHaveLength(0);
516+
expect(result.stats.totalScanned).toBe(1);
517+
});
518+
519+
it("should find issues with gh-aw-workflow-id XML marker", async () => {
520+
const issueWithWorkflowId = {
521+
id: "issue-with-id",
522+
number: 12670,
523+
title: "Test Issue with Workflow ID",
524+
url: "https://github.com/test-owner/test-repo/issues/12670",
525+
body: `# Test Issue
526+
527+
Some content here
528+
529+
> AI generated by [Smoke Copilot]()
530+
> - [x] expires on Jan 31, 2026, 6:05 AM UTC
531+
532+
<!-- gh-aw-workflow-id: smoke-copilot -->`,
533+
createdAt: "2026-01-30T04:05:02Z",
534+
};
535+
536+
mockGithub = {
537+
graphql: vi.fn().mockResolvedValue({
538+
repository: {
539+
issues: {
540+
pageInfo: { hasNextPage: false, endCursor: null },
541+
nodes: [issueWithWorkflowId],
542+
},
543+
},
544+
}),
545+
};
546+
547+
const result = await searchEntitiesWithExpiration(mockGithub, owner, repo, {
548+
entityType: "issues",
549+
graphqlField: "issues",
550+
resultKey: "issues",
551+
});
552+
553+
// This issue should be found because it has gh-aw-workflow-id marker
554+
expect(result.items).toHaveLength(1);
555+
expect(result.items[0]).toEqual(issueWithWorkflowId);
556+
expect(result.stats.totalScanned).toBe(1);
557+
});
558+
559+
it("should find issues with gh-aw-agentic-workflow XML marker", async () => {
560+
const issueWithAgenticWorkflow = {
561+
id: "issue-with-agentic",
562+
number: 12671,
563+
title: "Test Issue with Agentic Workflow",
564+
url: "https://github.com/test-owner/test-repo/issues/12671",
565+
body: `# Test Issue
566+
567+
Content goes here
568+
569+
> AI generated by [Test Workflow](https://example.com)
570+
> - [x] expires on Feb 1, 2026, 10:00 AM UTC
571+
572+
<!-- gh-aw-agentic-workflow: Test Workflow, run: https://example.com -->`,
573+
createdAt: "2026-01-30T04:05:02Z",
574+
};
575+
576+
mockGithub = {
577+
graphql: vi.fn().mockResolvedValue({
578+
repository: {
579+
issues: {
580+
pageInfo: { hasNextPage: false, endCursor: null },
581+
nodes: [issueWithAgenticWorkflow],
582+
},
583+
},
584+
}),
585+
};
586+
587+
const result = await searchEntitiesWithExpiration(mockGithub, owner, repo, {
588+
entityType: "issues",
589+
graphqlField: "issues",
590+
resultKey: "issues",
591+
});
592+
593+
// This issue should be found because it has gh-aw-agentic-workflow marker
594+
expect(result.items).toHaveLength(1);
595+
expect(result.items[0]).toEqual(issueWithAgenticWorkflow);
596+
expect(result.stats.totalScanned).toBe(1);
597+
});
471598
});

0 commit comments

Comments
 (0)