Skip to content

ci: close the issues a resolver-merged PR names (close-issues job) - #922

Merged
github-actions[bot] merged 2 commits into
mainfrom
ci/resolver-closes-issues
Nov 7, 2026
Merged

github-actions[bot] merged 2 commits into
mainfrom
ci/resolver-closes-issues

Conversation

@cdeust

@cdeust cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Problem

GitHub does not act on closing keywords (Closes #N) when the merge is made with the workflow's GITHUB_TOKEN, and that is how the resolver merges (PUT /pulls/N/merge in pipeline-post-merge.yml). The history of merged PRs that carry a closing keyword shows the split:

Merged by PRs Issue closed automatically
cdeust 23 (#639 … #828) all, within minutes
resolver (github-actions[bot]) 8 (#818, #819, #825, #829, #857, #859, #862, #908) none. #680, #682, #683, #685, #693, #699 and #714 were closed by hand on 2026-11-05 at 14:34; #801 was closed by hand today.

Every PR the resolver merges with Closes #N therefore leaves a finished issue open.

Change

  • New close-issues job in pipeline-post-merge.yml.
    • It runs after a successful resolve, outside the test chain, so a failure shows red without blocking the merged commit's tests.
    • It reads the PR's closingIssuesReferences over GraphQL; that is GitHub's own parse of the keywords, not a regex of ours.
    • It refuses a PR that is not merged.
    • It comments on each open issue, then closes it with state_reason: completed, and leaves issues that are already closed alone.
    • Permissions: issues: write, pull-requests: read, nothing else.
  • resolve exposes pr_number, which its script already wrote to GITHUB_OUTPUT.
  • pipeline-after-merge.yml (the caller of the reusable workflow) grants issues: write. The contracts name the new permission and job: ci_pipeline_routes.py for the caller's permissions, and ci_pipeline_profiles.py for POST_MERGE_NEEDS and the condition of the job.

This is the workflow's GITHUB_TOKEN. The Claude App permissions and branch protection are untouched.

Tests

tests_py/scripts/test_ci_resolver_closes_issues.py runs the job's own script against a fake API:

  • an open linked issue is commented, then closed as completed;
  • an issue that is already closed is left alone;
  • a PR that is not merged stops the job;
  • GraphQL errors stop the job;
  • the job's contract holds: it needs resolve, its condition is resolve success, its only permissions are issues: write and pull-requests: read, and pr_number is exposed.

87 tests and 312 subtests pass across the CI contract suites (check_ci_gate_complete, ci_pipeline_routes, ci_pipeline_source_contract, the resolver's verdict tests). The no-main-comparison guard passes, and scripts/check_ci_gate_complete.py reports OK. ruff check and ruff format --check pass on the touched files; the 2 E501 left in ci_pipeline_source_contract.py predate this PR. scripts/check_craftsmanship.py: OK.

The first real proof will come after merge: the next resolver merge of a PR with Closes #N.

GitHub does not act on closing keywords when the merge is made with the
workflow's GITHUB_TOKEN, which is how the resolver merges (PUT /pulls/N/merge).
All 8 resolver merges with a closing keyword left their issue open (#801 after
#908; #680, #682, #683, #685, #693, #699, #714 were closed by hand on
2026-11-05), while the merges by cdeust closed theirs within minutes.

A close-issues job, after a successful resolve and outside the test chain, reads
the PR's closingIssuesReferences (GitHub's own parse of the keywords), comments
and closes each open one with state_reason completed. It holds issues: write and
pull-requests: read only; the after-merge caller grants issues: write, and the
route and profile contracts name the new permission and job.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T23:49:18.946079Z 23e9e04 New commits
🔒 Security Review ✅ Completed 2026-11-06T23:52:18.939316Z 23e9e04 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 26s —— View job


PR #922 review (1 of 3)

Blockers:

  1. Wrong issue closed (pipeline-post-merge.yml:498, :539). If the PR references an issue in another repository (Closes other/repo#N), the job closes Cortex's own #N, because the query does not fetch repository { nameWithOwner }.
  2. Issues past the 50th are skipped (pipeline-post-merge.yml:498). closingIssuesReferences(first: 50) never checks pageInfo.hasNextPage, so any extra issues stay open with no error. CLAUDE.md forbids silent fallbacks.

There are also 3 notes, no action needed before merge: the workflow file is generated by render-ci but was edited by hand, the job comments before it closes the issue, and on PRs a person merged it can post "merged by the resolver".

Verdict: block

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b9860264b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if issue["state"] != "OPEN":
print(f"#{issue['number']} already {issue['state'].lower()}")
continue
path = f"{API}/repos/{repository}/issues/{issue['number']}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Verify the linked issue's repository before closing it

When a merged PR references an open issue in another repository, this constructs the write URL using GITHUB_REPOSITORY rather than the linked issue's repository. GitHub supports cross-repository closing references such as Fixes owner/other-repo#100 (documentation). Because the query only retrieves number and state, the job would comment on and close this repository's unrelated issue #100 instead, if that number exists locally. Retrieve the issue's repository identity and skip references outside the current repository before performing any writes.

Useful? React with 👍 / 👎.

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Review 1 of 3: PR #922 (close-issues job), commit 5b98602

I ran five reviews (code quality, security, tests, performance, docs) and traced each finding below in the diff myself.

Blockers

1. A reference to an issue in another repository closes the issue with the same number in Cortex. .github/workflows/pipeline-post-merge.yml:498 and :539

  • GitHub's closingIssuesReferences includes issues in other repositories, for example Closes someone/other#12 in the PR body or a Development-sidebar link.
  • The query fetches only { number state }. Line 539 then builds the path from this repository: /repos/{GITHUB_REPOSITORY}/issues/{number}.
  • So such a PR comments on and closes Cortex hooks.json file fix (needed on my system anyway) #12 as completed. That can be an unrelated issue, or an open PR, because issues and PRs share numbers and the issues endpoint closes both.
  • The state check reads the other repository's issue, not Cortex's, so it does not prevent this.
  • Fix: select repository { nameWithOwner } on each node and skip any node that is not GITHUB_REPOSITORY, or refuse it with a message. Add a test where a node from another repository produces no POST and no PATCH.

2. Linked issues past the 50th are dropped without any error. .github/workflows/pipeline-post-merge.yml:498

  • closingIssuesReferences(first: 50) never reads pageInfo.hasNextPage, so a PR linking more than 50 issues leaves the rest open with no error.
  • CLAUDE.md forbids silent fallbacks.
  • Fix: query pageInfo { hasNextPage } and raise SystemExit when it is true, or paginate.

Notes, no action before merge

  • The file header says pipeline-post-merge.yml is generated by render-ci ("edit the template"), and CONTRIBUTING.md:122 agrees. Unless the zetetic-team-subagents template gets the same change, the next render will remove close-issues and pr_number, and ci_pipeline_profiles.py will stop matching the rendered file.
  • The job posts the comment before the PATCH (:541-542). If the PATCH fails, the issue keeps a "Closed by #N" comment but stays open, and a re-run posts the comment again. Closing first, then commenting, avoids both.
  • resolve also succeeds for PRs a person already merged (:399-401). On those PRs this job can race GitHub's own closing and post "merged by the resolver", which is wrong.

Checked, no problem found:

  • Permissions: narrowed to issues: write and pull-requests: read on this job only.
  • Injection: PR_NUMBER arrives through env: into a quoted heredoc and is passed through int().
  • workflow_run boundary: no code from the PR is checked out.
  • Test chain: no job needs close-issues, so its failure cannot block the tests.

View job run

Verdict: block

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

CI autopilot : cette branche a 3 commits de retard sur main (3dbc44f), et un humain doit la rebaser. Elle modifie .github/workflows/. Le jeton de la routine n'a pas la permission workflows, donc il ne peut pas pousser sur cette branche. Les checks du head 5b98602 sont tous verts. Il n'y a pas de hold.


Generated by Claude Code

…job)

Review 1 blockers: a node from another repository (Closes other/repo#12) would
close the issue with the same number here, so nodes are filtered on
repository.nameWithOwner; more than 50 links stops the job (pageInfo.hasNextPage)
instead of dropping the rest. Notes: the issue is closed before it is commented,
so a failed close leaves no 'Closed by' comment; a PR a person merged is left to
GitHub (mergedBy.login != github-actions). Checked live, read-only: on #908 the
script reports '#801 already closed', on #828 'merged by cdeust: GitHub closes its
issues'.
@cdeust

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Remarques bloquantes de la review 1 traitées dans le dernier fixup! : les références vers un autre dépôt sont filtrées sur repository.nameWithOwner, et plus de 50 liens arrêtent le job. Les notes aussi : l'issue est fermée avant d'être commentée, et une PR fusionnée par une personne est laissée à GitHub. J'ai vérifié en lecture seule sur l'API réelle : #908 donne #801 already closed, #828 donne merged by cdeust.

Note « fichier généré » : le gabarit render-ci vit dans zetetic-team-subagents, hors de ce dépôt. Les ajustements du résolveur propres à Cortex (#870, #873) ont été faits de la même manière, directement dans le fichier rendu, avec les contrats de scripts/ mis à jour. Un nouveau rendu devra reporter close-issues, comme il doit reporter ces ajustements ; check_ci_gate_complete.py signalera l'écart.

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 1m 37s —— View job


PR #922 review (2 of 3)

  • Checked the 2 blockers from review 1 against fixup 23e9e04: both are fixed
  • Ran five reviews (quality, security, tests, performance, docs) on 23e9e04 only and traced each finding myself
  • Run the tests: not done. Running pytest and ruff needs approval in this job, so this review relies on the PR's reported results.
  • Posted the review: ci: close the issues a resolver-merged PR names (close-issues job) #922 (comment)

Review 1 blockers:

  1. Issue in another repository: fixed. Nodes are now filtered on repository.nameWithOwner, and a test covers it.
  2. More than 50 linked issues: fixed. hasNextPage now stops the job before any write.

New blockers: none.

Notes, no action before merge:

  • If mergedBy is null, the job exits green after printing "merged by None".
  • The over-50 test does not check that nothing was written.
  • A re-run closes an issue a maintainer reopened and posts a second comment.

Verdict: approve

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Review 2 of 3: PR #922 (close-issues job), fixup 23e9e04

Review 1 blockers: both fixed

  1. Issue in another repository. Fixed in pipeline-post-merge.yml:549-552. The query now selects repository { nameWithOwner }, and any node from a different repository is skipped before a write. test_an_issue_of_another_repository_is_left_alone asserts the only call is the query, and it would fail if the filter were removed.
  2. More than 50 linked issues. Fixed in :546-547. pageInfo { hasNextPage } now stops the job before the loop starts.

Two of review 1's notes are also handled: the issue is now closed before the comment, and a PR a person merged is left to GitHub (mergedBy.login != "github-actions").

Blockers

None. I ran five reviews (quality, security, tests, performance, docs) on 23e9e04 only and traced each finding myself.

Notes, no action before merge

  • If mergedBy is null (:540-544), the job prints "merged by None" and exits green. It would be clearer to raise SystemExit and take the skip path only when a login is present.
  • test_more_than_fifty_linked_issues_stop_the_job checks only that SystemExit is raised. It still passes if the hasNextPage check moves below the loop. Asserting that no write happened would catch that.
  • A re-run of the workflow re-closes an issue a maintainer reopened and posts a second "Closed by" comment (:553-560), because the only check is state != "OPEN".

I could not run pytest or ruff here because the commands need approval. The PR reports that both pass.

Verdict: approve

@github-actions
github-actions Bot merged commit 33e555c into main Nov 7, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant