Issues here carry no state between open and closed. An issue whose PR is under
review, an issue merged to dev and waiting for a release, and an issue nobody
has touched all look the same on the issue list. Nothing closes on its own
either, so closing is a manual sweep that gets forgotten.
Why nothing closes today
GitHub honors Closes #NN only when the PR merges into the repository's default
branch. The default here is master, and every feature PR targets dev under
git flow, so the keyword never fires. Promoting dev to master does not fire
it either, because the promotion's merge commit carries no keyword of its own.
Verified on 2026-08-17: closedByPullRequestsReferences is empty for #36, #48,
#49 and #53, every one of which has a merged PR whose body says Closes #NN.
GitHub records no closing link at all for a PR based on a non-default branch, so
the automation has to own that link and parse PR bodies itself.
What this adds
An organization level project with a Status field: Todo, In progress,
In review, Ready for release, Done.
| Trigger |
Transition |
First push of an issue-<number>-<slug> branch |
In progress, only from Todo or unset |
| PR opened, reopened, ready for review, or edited |
In review |
PR merged into dev |
Ready for release |
A release ships from master |
closed, and the board's own workflow moves it to Done |
Closing by containment
An issue closes when the commit that fixed it is contained in the commit being
released, rather than by clearing the Ready for release column. An immediate
fix branched off master ships a release while dev still holds merged work,
and clearing the column would close issues that are not in the shipped jar.
| Case |
Contained? |
Result |
Merged to dev, dev promoted to master |
yes |
closes |
Merged to dev, an immediate fix released instead |
no |
stays in Ready for release |
Immediate fix merged straight to master |
yes |
closes, though it never entered Ready for release |
The third row is why containment is the rule rather than a patch on top of
column scanning: an immediate fix never passes through dev, so the
Ready for release automation never touches it, and containment still closes it.
Scope
scripts/issue-status.sh, pure parsing, unit tested.
scripts/set-issue-status.sh, the Projects v2 writes.
scripts/close-released-issues.sh, called from a new final step in
release.yml.
.github/workflows/issue-status.yml for the push and pull request triggers.
CLAUDE.md records that branch names and closing keywords are now load
bearing rather than habit.
Nothing here ships to server owners, so no changeset is needed.
Issues here carry no state between open and closed. An issue whose PR is under
review, an issue merged to
devand waiting for a release, and an issue nobodyhas touched all look the same on the issue list. Nothing closes on its own
either, so closing is a manual sweep that gets forgotten.
Why nothing closes today
GitHub honors
Closes #NNonly when the PR merges into the repository's defaultbranch. The default here is
master, and every feature PR targetsdevundergit flow, so the keyword never fires. Promoting
devtomasterdoes not fireit either, because the promotion's merge commit carries no keyword of its own.
Verified on 2026-08-17:
closedByPullRequestsReferencesis empty for #36, #48,#49 and #53, every one of which has a merged PR whose body says
Closes #NN.GitHub records no closing link at all for a PR based on a non-default branch, so
the automation has to own that link and parse PR bodies itself.
What this adds
An organization level project with a
Statusfield:Todo,In progress,In review,Ready for release,Done.issue-<number>-<slug>branchIn progress, only fromTodoor unsetIn reviewdevReady for releasemasterDoneClosing by containment
An issue closes when the commit that fixed it is contained in the commit being
released, rather than by clearing the
Ready for releasecolumn. An immediatefix branched off
masterships a release whiledevstill holds merged work,and clearing the column would close issues that are not in the shipped jar.
dev,devpromoted tomasterdev, an immediate fix released insteadReady for releasemasterReady for releaseThe third row is why containment is the rule rather than a patch on top of
column scanning: an immediate fix never passes through
dev, so theReady for releaseautomation never touches it, and containment still closes it.Scope
scripts/issue-status.sh, pure parsing, unit tested.scripts/set-issue-status.sh, the Projects v2 writes.scripts/close-released-issues.sh, called from a new final step inrelease.yml..github/workflows/issue-status.ymlfor the push and pull request triggers.CLAUDE.mdrecords that branch names and closing keywords are now loadbearing rather than habit.
Nothing here ships to server owners, so no changeset is needed.