Skip to content

Duplicate Code: Expired Entity Cleanup Handlers Repeated Across Issue/PR/Discussion Scripts #22370

@github-actions

Description

@github-actions

Overview

The latest commit (ede35d6767817737063d25a155df2c81f8386a07) includes significant structural duplication in expired-entity cleanup handlers under actions/setup/js/.

The close-and-comment orchestration logic is implemented separately for issues, pull requests, and discussions with only entity-specific API differences. This exceeds the reporting threshold (>10 duplicated lines and 3+ similar occurrences).

Critical Information

  • Severity: Medium
  • Pattern type: Structural duplication / copy-variant handlers
  • Occurrences: 3
  • Primary impact: Maintenance overhead and drift risk in expiration behavior

Duplication Details

Pattern: Expired entity close flow duplicated per entity type

  • actions/setup/js/close_expired_issues.cjs:49
  • actions/setup/js/close_expired_pull_requests.cjs:48
  • actions/setup/js/close_expired_discussions.cjs:92

All three files independently implement the same high-level workflow:

  1. Resolve repo/workflow metadata
  2. Invoke executeExpiredEntityCleanup(...) with entity metadata
  3. Build closing message from expiration date + footer
  4. Add comment
  5. Close entity
  6. Return normalized { status, record }
Representative duplicated block (issue vs PR handlers)
const { workflowName, workflowId, runUrl } = getWorkflowMetadata(owner, repo);

await executeExpiredEntityCleanup(github, owner, repo, {
  entityType: "...",
  graphqlField: "...",
  resultKey: "...",
  entityLabel: "...",
  summaryHeading: "...",
  processEntity: async entity => {
    const closingMessage = `This ... was automatically closed because it expired on \$\{entity.expirationDate.toISOString()}.` + generateExpiredEntityFooter(workflowName, runUrl, workflowId);
    await add...Comment(github, owner, repo, entity.number, closingMessage);
    await close...(github, owner, repo, entity.number);
    return { status: "closed", record: { number: entity.number, url: entity.url, title: entity.title } };
  },
});
Notes on discussion variant

close_expired_discussions.cjs adds duplicate-avoidance checks (hasExpirationComment, closed-state handling), but still duplicates the same metadata acquisition, cleanup invocation structure, message generation, comment/close sequencing, and normalized result mapping.

Impact Analysis

  • Maintainability: Policy changes (message/footer format, status mapping, logging style) must be updated in 3 places.
  • Bug Risk: One handler can diverge silently from others (e.g., footer composition, closing semantics, dedupe behavior).
  • Code Bloat: Repeated orchestration obscures the true entity-specific differences.

Refactoring Recommendations

  1. Extract a shared expired-entity adapter factory
  • Add a helper like buildExpiredEntityProcessor(config) in actions/setup/js/expired_entity_main_flow.cjs (or a new expired_entity_adapters.cjs).
  • Keep only API-specific operations (comment/close/checkExisting) in per-entity files.
  • Estimated effort: Medium (3-5 hours).
  1. Move message generation and record shaping to shared utilities
  • Centralize closing message and {status, record} shaping.
  • Keep discussions-specific dedupe hook as optional callback.
  • Estimated effort: Low-Medium (2-3 hours).

Implementation Checklist

  • Define shared processor/adapters interface for expired entities
  • Refactor issue and PR handlers to use shared adapter
  • Refactor discussion handler to use shared adapter + dedupe hooks
  • Add/adjust tests for consistent behavior across entity types
  • Verify no behavior regression for comments, closure, and summary output

Analysis Metadata

  • Analyzed files (changed, non-test): 601 .go files in pkg/, 238 .cjs files in actions/setup/js/
  • Detection method: Serena semantic analysis for Go hotspots + structural comparison in changed .cjs handlers
  • Commit: ede35d6767817737063d25a155df2c81f8386a07
  • Workflow run: §23420898530
  • Analysis date: 2026-03-23 UTC

Warning

**⚠️ Firewall blocked 1 domain**

The following domain was blocked by the firewall during workflow execution:

  • ab.chatgpt.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "ab.chatgpt.com"

See Network Configuration for more information.

Generated by Duplicate Code Detector ·

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions