Skip to content

Duplicate Code: Expired Entity Cleanup in close_expired_* scripts #12819

@github-actions

Description

@github-actions

🔍 Duplicate Code Detected: Expired Entity Cleanup Logic

Analysis of commit 1659ea6

Assignee: @copilot

Summary

The expired-entity cleanup scripts for issues, pull requests, and discussions duplicate the same search/filter loop, expiration matching, and pagination logic. The three files are structurally identical in large sections (>70 lines each) with only entity-specific fields and API endpoints differing.

Duplication Details

Pattern: Expired entity GraphQL search + filter loop

  • Severity: Medium
  • Occurrences: 3
  • Locations:
    • actions/setup/js/close_expired_issues.cjs (lines 22-123)
    • actions/setup/js/close_expired_pull_requests.cjs (lines 22-123)
    • actions/setup/js/close_expired_discussions.cjs (lines 22-139)
  • Code Sample:
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function searchIssuesWithExpiration(github, owner, repo) {
  const issues = [];
  let hasNextPage = true;
  let cursor = null;
  let pageCount = 0;
  let totalScanned = 0;

  core.info(`Starting GraphQL search for open issues in ${owner}/${repo}`);

  while (hasNextPage) {
    pageCount++;
    core.info(`Fetching page ${pageCount} of open issues (cursor: ${cursor || "initial"})`);

    const query = `
      query($owner: String!, $repo: String!, $cursor: String) {
        repository(owner: $owner, name: $repo) {
          issues(first: 100, after: $cursor, states: [OPEN]) {
            pageInfo { hasNextPage endCursor }
            nodes { id number title url body createdAt }
          }
        }
      }
    `;

    const result = await github.graphql(query, { owner, repo, cursor });
    if (!result || !result.repository || !result.repository.issues) {
      core.warning(`GraphQL query returned no data at page ${pageCount}`);
      break;
    }

    const nodes = result.repository.issues.nodes || [];
    totalScanned += nodes.length;

    let agenticCount = 0;
    let withExpirationCount = 0;

    for (const issue of nodes) {
      const agenticPattern = /^> AI generated by/m;
      const isAgenticWorkflow = issue.body && agenticPattern.test(issue.body);
      if (!isAgenticWorkflow) continue;

      const match = issue.body ? issue.body.match(EXPIRATION_PATTERN) : null;
      if (match) {
        withExpirationCount++;
        core.info(`  Found issue #${issue.number} with expiration marker: "${match[1]}" - ${issue.title}`);
        issues.push(issue);
      }
    }

    core.info(`Page ${pageCount} summary: ${agenticCount} agentic issues, ${withExpirationCount} with expiration markers`);
    hasNextPage = result.repository.issues.pageInfo.hasNextPage;
    cursor = result.repository.issues.pageInfo.endCursor;
  }

  core.info(`Search complete: Scanned ${totalScanned} issues across ${pageCount} pages, found ${issues.length} with expiration markers`);
  return { issues, stats: { pageCount, totalScanned } };
}

Impact Analysis

  • Maintainability: Changes to pagination, filtering rules, or logging must be replicated across three files.
  • Bug Risk: Divergence is already visible (discussions adds duplicate ID handling). Future changes could accidentally drift.
  • Code Bloat: ~70+ lines repeated per file, increasing review surface area.

Refactoring Recommendations

  1. Extract shared pagination + filtering helper

    • Extract a common helper in actions/setup/js/expiration_helpers.cjs (or a new expired_entity_search.cjs) that accepts entity type metadata (query fragment, node mapping, label/ID fields).
    • Estimated effort: medium (2-4 hours)
    • Benefits: single place for pagination and agentic + expiration matching.
  2. Normalize logging + stats structure

    • Return a normalized {items, stats} shape, with optional dedupe callback for discussions.
    • Estimated effort: low (1-2 hours)
    • Benefits: reduces drift and makes future additions (e.g., projects) straightforward.

Implementation Checklist

  • Review duplication findings
  • Prioritize refactoring tasks
  • Create refactoring plan
  • Implement changes
  • Update tests
  • Verify no functionality broken

Analysis Metadata

  • Analyzed Files: 3
  • Detection Method: Serena semantic code analysis + targeted diff review
  • Commit: 1659ea6
  • Analysis Date: 2026-01-30

AI generated by Duplicate Code Detector

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions