33
44const { getCloseOlderDiscussionMessage } = require ( "./messages_close_discussion.cjs" ) ;
55const { getErrorMessage } = require ( "./error_helpers.cjs" ) ;
6- const { getWorkflowIdMarkerContent, generateWorkflowIdMarker, generateWorkflowCallIdMarker } = require ( "./generate_footer.cjs" ) ;
6+ const { getWorkflowIdMarkerContent, generateWorkflowIdMarker, generateWorkflowCallIdMarker, generateCloseKeyMarker , getCloseKeyMarkerContent } = require ( "./generate_footer.cjs" ) ;
77const { sanitizeContent } = require ( "./sanitize_content.cjs" ) ;
88const { closeOlderEntities, MAX_CLOSE_COUNT : SHARED_MAX_CLOSE_COUNT } = require ( "./close_older_entities.cjs" ) ;
99
@@ -29,26 +29,42 @@ const GRAPHQL_DELAY_MS = 500;
2929 * When set, filters by the `gh-aw-workflow-call-id` marker so callers sharing the same
3030 * reusable workflow do not close each other's discussions. Falls back to `gh-aw-workflow-id`
3131 * when not provided (backward compat for discussions created before this fix).
32+ * @param {string } [closeOlderKey] - Optional explicit deduplication key. When set, the
33+ * `gh-aw-close-key` marker is used as the primary search term and exact filter instead
34+ * of the workflow-id / workflow-call-id markers.
3235 * @returns {Promise<Array<{id: string, number: number, title: string, url: string}>> } Matching discussions
3336 */
34- async function searchOlderDiscussions ( github , owner , repo , workflowId , categoryId , excludeNumber , callerWorkflowId ) {
37+ async function searchOlderDiscussions ( github , owner , repo , workflowId , categoryId , excludeNumber , callerWorkflowId , closeOlderKey ) {
3538 core . info ( `Starting search for older discussions in ${ owner } /${ repo } ` ) ;
3639 core . info ( ` Workflow ID: ${ workflowId || "(none)" } ` ) ;
3740 core . info ( ` Exclude discussion number: ${ excludeNumber } ` ) ;
3841
39- if ( ! workflowId ) {
40- core . info ( "No workflow ID provided - cannot search for older discussions" ) ;
42+ if ( ! workflowId && ! closeOlderKey ) {
43+ core . info ( "No workflow ID or close-older-key provided - cannot search for older discussions" ) ;
4144 return [ ] ;
4245 }
4346
44- // Build GraphQL search query
45- // Search for open discussions with the workflow-id marker in the body
46- const workflowIdMarker = getWorkflowIdMarkerContent ( workflowId ) ;
47- // Escape quotes in workflow ID to prevent query injection
48- const escapedMarker = workflowIdMarker . replace ( / " / g, '\\"' ) ;
49- let searchQuery = `repo:${ owner } /${ repo } is:open "${ escapedMarker } " in:body` ;
50-
51- core . info ( ` Added workflow ID marker filter to query: "${ escapedMarker } " in:body` ) ;
47+ // Build GraphQL search query.
48+ // When a close-older-key is provided it becomes the primary search term; otherwise
49+ // fall back to the workflow-id marker.
50+ let searchQuery ;
51+ let exactMarker ;
52+ if ( closeOlderKey ) {
53+ const closeKeyMarkerContent = getCloseKeyMarkerContent ( closeOlderKey ) ;
54+ const escapedMarker = closeKeyMarkerContent . replace ( / " / g, '\\"' ) ;
55+ searchQuery = `repo:${ owner } /${ repo } is:open "${ escapedMarker } " in:body` ;
56+ exactMarker = generateCloseKeyMarker ( closeOlderKey ) ;
57+ core . info ( ` Using close-older-key for search: "${ escapedMarker } " in:body` ) ;
58+ } else {
59+ // Build GraphQL search query
60+ // Search for open discussions with the workflow-id marker in the body
61+ const workflowIdMarker = getWorkflowIdMarkerContent ( workflowId ) ;
62+ // Escape quotes in workflow ID to prevent query injection
63+ const escapedMarker = workflowIdMarker . replace ( / " / g, '\\"' ) ;
64+ searchQuery = `repo:${ owner } /${ repo } is:open "${ escapedMarker } " in:body` ;
65+ exactMarker = callerWorkflowId ? generateWorkflowCallIdMarker ( callerWorkflowId ) : generateWorkflowIdMarker ( workflowId ) ;
66+ core . info ( ` Added workflow ID marker filter to query: "${ escapedMarker } " in:body` ) ;
67+ }
5268 core . info ( `Executing GitHub search with query: ${ searchQuery } ` ) ;
5369
5470 const result = await github . graphql (
@@ -84,18 +100,16 @@ async function searchOlderDiscussions(github, owner, repo, workflowId, categoryI
84100 // 1. Must not be the excluded discussion (newly created one)
85101 // 2. Must not be already closed
86102 // 3. If categoryId is specified, must match
87- // 4. Body must contain the exact marker for this workflow.
88- // When callerWorkflowId is set, match `gh-aw-workflow-call-id` so that callers
89- // sharing the same reusable workflow do not close each other's discussions.
103+ // 4. Body must contain the exact marker. When closeOlderKey is set the close-key marker
104+ // is used. Otherwise, when callerWorkflowId is set, match `gh-aw-workflow-call-id` so
105+ // that callers sharing the same reusable workflow do not close each other's discussions.
90106 // Fall back to `gh-aw-workflow-id` for backward compat with older discussions.
91107 core . info ( "Filtering search results..." ) ;
92108 let filteredCount = 0 ;
93109 let excludedCount = 0 ;
94110 let closedCount = 0 ;
95111 let markerMismatchCount = 0 ;
96112
97- const exactMarker = callerWorkflowId ? generateWorkflowCallIdMarker ( callerWorkflowId ) : generateWorkflowIdMarker ( workflowId ) ;
98-
99113 const filtered = result . search . nodes
100114 . filter (
101115 /** @param {any } d */ d => {
@@ -215,9 +229,10 @@ async function closeDiscussionAsOutdated(github, owner, repo, discussionId) {
215229 * @param {string } workflowName - Name of the workflow
216230 * @param {string } runUrl - URL of the workflow run
217231 * @param {string } [callerWorkflowId] - Optional calling workflow identity for precise filtering
232+ * @param {string } [closeOlderKey] - Optional explicit deduplication key for close-older matching
218233 * @returns {Promise<Array<{number: number, url: string}>> } List of closed discussions
219234 */
220- async function closeOlderDiscussions ( github , owner , repo , workflowId , categoryId , newDiscussion , workflowName , runUrl , callerWorkflowId ) {
235+ async function closeOlderDiscussions ( github , owner , repo , workflowId , categoryId , newDiscussion , workflowName , runUrl , callerWorkflowId , closeOlderKey ) {
221236 const result = await closeOlderEntities (
222237 github ,
223238 owner ,
@@ -229,9 +244,10 @@ async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId
229244 {
230245 entityType : "discussion" ,
231246 entityTypePlural : "discussions" ,
232- // Use a closure so callerWorkflowId is forwarded to searchOlderDiscussions without going
233- // through the closeOlderEntities extraArgs mechanism (which appends excludeNumber last)
234- searchOlderEntities : ( gh , o , r , wid , categoryId , excludeNumber ) => searchOlderDiscussions ( gh , o , r , wid , categoryId , excludeNumber , callerWorkflowId ) ,
247+ // Use a closure so callerWorkflowId and closeOlderKey are forwarded to
248+ // searchOlderDiscussions without going through the closeOlderEntities extraArgs
249+ // mechanism (which appends excludeNumber last)
250+ searchOlderEntities : ( gh , o , r , wid , categoryId , excludeNumber ) => searchOlderDiscussions ( gh , o , r , wid , categoryId , excludeNumber , callerWorkflowId , closeOlderKey ) ,
235251 getCloseMessage : params =>
236252 getCloseOlderDiscussionMessage ( {
237253 newDiscussionUrl : params . newEntityUrl ,
0 commit comments