Skip to content

cross-repo-issue-closer.yml: a qualified closing keyword pointing at a foreign PULL REQUEST would close that PR, which GitHub's own keyword parser never does #9711

Description

@os-steve

Filed unassigned by the domain:devx os-dev seat while landing #9643. Recording, not claiming — it came out of that card's H1/H4 audit of the per-target loop and is a different defect class (wrong target kind, not wrong target state), so it was left out of that PR rather than folded in.

Dedup-searched (workflow name, "pull request target", "closing keyword"): only #9575, #9595 and #9643 name this file, and none covers this.

The path

.github/workflows/cross-repo-issue-closer.yml, the per-target loop. The target regex accepts any qualified owner/repo#N:

constpattern=newRegExp(`\\b(?:${KEYWORDS})\\s+([\\w.-]+)\\/([\\w.-]+)#(\\d+)\\b`,'gi');

N is just digits. Nothing downstream asks what kind of thing N is, and issues.get will not tell it unless asked: on GitHub every pull request is also an issue, so GET /repos/{owner}/{repo}/issues/{N} returns a pull request happily, with state, state_reason and all — plus a pull_request key that the loop never reads.

So a merged PR whose body says Fixes objectstack-ai/objectui#4500, where 4500 happens to be a pull request rather than an issue, makes this workflow comment on that foreign PR and then close it.

Why that is wrong rather than merely odd

GitHub's own closing-keyword parser — the thing this workflow exists to emulate across repository boundaries — does not close pull requests. Fixes #N against a PR number creates a reference and nothing else. This workflow's whole warrant is "do across repos what GitHub already does within one"; closing a foreign PR is a power the behaviour it emulates does not have, exercised with a credential scoped for issue hygiene.

The blast radius is the asymmetric part. A wrongly-closed issue is reopened by anyone who notices. A wrongly-closed PR loses its merge-queue membership and any armed auto-merge in the same step, and neither returns by itself — the same one-way property AGENTS.md §7 records for the draft flip.

Severity, stated honestly

Low, and the reason is measured rather than assumed: over the 1176 most recently merged PRs there are zero qualified foreign closing keywords of any kind (re-measured twice on 2026-08-18, two different windows, both zero), so this branch has never had a target at all. It is cheap to get wrong and cheap to get right.

The fix, if it is wanted

One condition in the loop, next to the state/state_reason conditions #9643 added:

if(issue.pull_request){/* announce and skip -- this is a PR, not an issue */}

plus a scenario in scripts/check-cross-repo-closer-outcome.mjs (the doubles already model issues.get, so it is a fixture field and one assertion) and a matching mutation in --self-test. Whether the skip should be quiet, a core.warning, or red is the only judgement in it — a PR body naming a foreign PR with a closing keyword is more likely an authoring mistake than a deliberate instruction, which argues for at least an annotation.

Ref: #9643 · #9595 · #9575


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions