Skip to content

feat(ci): add reusable stale and PR labeler workflows#25

Merged
lml2468 merged 1 commit into
mainfrom
feat/automation-p0-shared-workflows
May 19, 2026
Merged

feat(ci): add reusable stale and PR labeler workflows#25
lml2468 merged 1 commit into
mainfrom
feat/automation-p0-shared-workflows

Conversation

@lml2468
Copy link
Copy Markdown
Contributor

@lml2468 lml2468 commented May 19, 2026

Summary

Adds two reusable workflows to the shared .github repository, available to all Mininglamp-OSS repos.


reusable-stale.yml

Four-lane stale management (modeled after openclaw best practice):

Lane Stale Close
Unassigned issues 14d 7d
Assigned issues 30d 10d
Unassigned PRs 14d 7d
Assigned PRs 27d 7d (deadline-based, ignores updates)
  • Exempt labels: maintainer, pinned, security, no-stale
  • close-issue-reason: not_planned
  • actions/stale pinned by commit SHA

reusable-pr-labeler.yml

  • Size labels: size/XS / size/S / size/M / size/L / size/XL (net diff lines, lockfiles excluded)
  • Dependency change detection: when go.mod, package.json, pnpm-lock.yaml, yarn.lock, go.sum are touched:
    • Adds dependencies-changed label
    • Posts/updates a maintainer checklist comment
    • Removes the label on sync if dependency files are no longer present
  • actions/github-script pinned by commit SHA

Reviewed by codex. Fixes applied: removed unused contents: read, added yarn.lock to dep detection, added label cleanup on sync.


How to use in repos

# .github/workflows/stale.yml
name: Stale
on:
  schedule:
    - cron: "0 3 * * *"
  workflow_dispatch:
permissions: {}
jobs:
  stale:
    uses: Mininglamp-OSS/.github/.github/workflows/reusable-stale.yml@main
    permissions:
      issues: write
      pull-requests: write
# .github/workflows/labeler.yml
name: PR Labeler
on:
  pull_request:
    types: [opened, synchronize, reopened]
permissions: {}
jobs:
  label:
    uses: Mininglamp-OSS/.github/.github/workflows/reusable-pr-labeler.yml@main
    with:
      pr_number: ${{ github.event.pull_request.number }}
      repo_owner: ${{ github.repository_owner }}
      repo_name: ${{ github.event.repository.name }}
    permissions:
      issues: write
      pull-requests: write
    secrets: inherit

Part of P0 automation rollout

Merge this first — business repos' stale.yml and labeler.yml depend on these reusable workflows being on main.

- reusable-stale.yml: four-lane stale management
  - Unassigned issues/PRs: 14d stale → 7d close
  - Assigned issues: 30d stale → 10d close
  - Assigned PRs: 27d stale → 7d close (deadline-based, ignores updates)
  - Exempt labels: maintainer, pinned, security, no-stale
- reusable-pr-labeler.yml: PR size auto-labeling + dependency-change detection
  - Size tiers XS/S/M/L/XL based on net diff lines (lockfiles excluded)
  - Detects go.mod/package.json/lockfile changes, adds dependencies-changed label
  - Posts/updates maintainer checklist comment; removes label on sync if resolved
  - Actions pinned by commit SHA
Copy link
Copy Markdown

@yujiawei yujiawei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — PR #25 (.github)

Summary

Two new reusable workflows for the org-wide .github repository:

  • reusable-stale.yml — four-lane stale management (unassigned issues+PRs, assigned issues, assigned PRs with deadline-based close)
  • reusable-pr-labeler.yml — size labels and dependency-change detection via actions/github-script

Both third-party actions are pinned by commit SHA. I verified the pins resolve to the upstream v9 / v7 tag commits:

  • actions/stale@5bef64f19d7facfb25b37b414482c7164d639639refs/tags/v9
  • actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673brefs/tags/v7

Permission scoping is correct: workflow-level permissions: {}, job-level issues: write + pull-requests: write only where needed. The four-lane stale design cleanly partitions assigned vs. unassigned via exempt-all-assignees: true and include-only-assigned: true — no double-processing.

Overall the structure is solid. Findings below are non-blocking hardening and polish suggestions.

Findings

P2 — Defense-in-depth: avoid ${{ inputs.* }} interpolation inside github-script

reusable-pr-labeler.yml lines 39–41:

const owner = '${{ inputs.repo_owner }}';
const repo = '${{ inputs.repo_name }}';
const prNumber = Number('${{ inputs.pr_number }}');

Direct expression interpolation into a script: body is the script-injection pattern that GitHub explicitly warns against (security hardening guide). In this deployment the callers pass github.repository_owner and github.event.repository.name, which are GitHub-controlled and safe — so there is no live exploit. But because this workflow is workflow_call-exposed to every repo in Mininglamp-OSS, a future caller (or a misuse like with: repo_owner: ${{ github.event.pull_request.title }}) could turn this into a code-execution sink in one keystroke.

The defense-in-depth pattern costs nothing:

- name: Apply size label and detect dependency changes
  uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
  env:
    REPO_OWNER: ${{ inputs.repo_owner }}
    REPO_NAME: ${{ inputs.repo_name }}
    PR_NUMBER: ${{ inputs.pr_number }}
  with:
    script: |
      const owner = process.env.REPO_OWNER;
      const repo = process.env.REPO_NAME;
      const prNumber = Number(process.env.PR_NUMBER);
      ...

Recommended given the security_sensitive classification.

P2 — "net diff lines" is a misnomer

reusable-pr-labeler.yml line 64:

netLines += (f.additions || 0) + (f.deletions || 0);

This is total churn (additions + deletions), not net lines. The PR body and the inline comment ("Calculate net lines excluding lockfiles") describe it as net; the size thresholds (<10 XS … ≥500 XL) were almost certainly tuned for churn. The math is fine — just rename the variable and update the description so future maintainers don't "fix" it into actual additions - deletions.

P2 — Orphaned dep-change comment when label is removed

reusable-pr-labeler.yml lines 132–141: when a sync push removes all dep files, the dependencies-changed label is removed, but the existing maintainer-checklist comment (matched by <!-- octo-dep-change-bot -->) is left in place. Result: the PR has no label but still shows a "Dependency Changes Detected" checklist. Minor UX inconsistency — either also delete/strikethrough the comment in this branch, or update its body to "no longer present".

P3 — Duplicate listLabelsOnIssue call

Lines 107 and 133 both call listLabelsOnIssue for the same PR within the same script run. Fold the second lookup into the first snapshot to save one API call per run.

P3 — operations-per-run: 500 is well above the default

All three stale jobs set operations-per-run: 500 (default is 30). Each "operation" consumes ~1 API call against the 1,000/hr GITHUB_TOKEN budget. At current Mininglamp-OSS scale this is fine, but on a very active repo a daily cron could spike the GraphQL/REST budget and starve other workflows running in the same hour. Worth keeping in mind if you ever consolidate orgs.

Nit — Caller permissions: block in PR description

The example caller snippets put a permissions: block on the calling job that uses the reusable workflow. This is correct — for a uses:-style job, job-level permissions: set the GITHUB_TOKEN scope for the called workflow, and the called workflow can only narrow further. Just confirming this is intentional and matches docs; nothing to change.

Security review (security_sensitive)

Things I'd want a human maintainer to double-check before merge — none are blockers, all are defense posture:

  • Script-injection surface — see P2 finding above. Today's callers are safe; future callers may not be.
  • Permission grants are minimal and correct. No contents: write, no id-token: write, no actions: write. The labeler createLabel call works against issues: write.
  • No secrets used. secrets: inherit in the example caller is harmless but unnecessary — the reusable workflow only uses the default GITHUB_TOKEN.
  • Trigger choice is safe. The labeler example uses pull_request (not pull_request_target), so PRs from forks get the read-only GITHUB_TOKEN and the label step will simply no-op rather than running attacker-controlled code with write access. (Trade-off: fork PRs won't get auto-labeled. Acceptable for internal-first repos.)
  • Action pins verified against upstream tag refs (see Summary).

Verdict

No correctness, security, or data-loss blockers. The script-injection hardening (P2) is strongly recommended for a shared reusable workflow and should be addressed in a follow-up if not in this PR. Approving so the downstream rollout PRs are unblocked.

@lml2468 lml2468 merged commit e1881c8 into main May 19, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants