Skip to content

cross-repo-issue-closer.yml: an already-closed foreign issue is skipped whole, so it never gets the backlink the workflow exists to leave #9643

Description

@os-steve

Filed unassigned by the domain:devx os-dev seat while landing #9595. Recording, not claiming — this came out of that card's H3 exit-path audit of the file. It is the one exit path where a judgment is attached, so it is a card rather than a rider on that PR. Dedup-searched (workflow name, "already closed", "backlink"): only #9575 and #9595 name this file, and neither covers this path.

The path

.github/workflows/cross-repo-issue-closer.yml, the per-target loop:

const{data: issue}=awaitgithub.rest.issues.get({ ... });if(issue.state==='closed'){core.info(`${key} is already closed — skipping.`);continue;}awaitgithub.rest.issues.createComment({ ... });// the backlinkawaitgithub.rest.issues.update({ ... });// the close

The skip covers both calls, but only one of them is redundant. Closing an already-closed issue is a no-op worth avoiding. Leaving the PR link on it is not: the file's own header names the missing backlink as half the defect the workflow exists to fix —

and leaves that issue open forever — with no reference to the PR on the issue's own page either, so the next reader has no way to find the fix

— and that second clause is exactly what the skip drops.

Why the case is realistic rather than theoretical

The scenario the header cites is the scenario that produces it. v17 verification (#4482) left objectui#4475 and objectui#4478 open, and both had to be closed by hand. A human closing a foreign issue by hand before the fixing PR merges is the ordinary sequence — the fix ships, someone tidies the tracker, then the merge happens. The loop then finds state === 'closed', skips, and the issue keeps no pointer to the PR that fixed it. The workflow reports a clean green run about it.

Same for a repeat run: re-running the job after a partial failure (now that #9595 makes such a failure red and re-runnable) hits this path for every target that succeeded the first time, which is correct for the close and, again, silently correct-looking for the backlink.

The judgment this needs, which is why it is not folded into #9595

Three defensible answers, and they are not obviously ranked:

  1. Comment, then skip the close. The backlink always lands; the cost is a comment on an issue somebody already closed, which some readers experience as noise on a settled thread.
  2. Comment only when no comment from this workflow is already there. Needs an idempotency marker in the comment body (merge-queue-triage.yml uses a per-run HTML marker; here it would have to be per-PR) and a listComments call per target — more API surface, and a listing that cannot be read has to degrade in a stated direction.
  3. Leave it. Defensible if the intended reading is "an already-closed issue has an owner who knows about it", but then the header's own reasoning should say so, because today it argues the opposite.

Whichever wins, the core.info line should probably say what was skipped and what that costs, rather than only that something was skipped.

Scenario L2 in scripts/check-cross-repo-closer-outcome.mjs currently pins today's behaviour (no comment, no close, the other targets still handled) and would be the place to move the pin.

Ref: #4482 · #9575 · #9595 · PR #9594


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions