From 6b17e81361e26de577d1b5412eb90a84f5f10609 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 07:59:31 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(ci):=20rename=20the=20cross-repo=20clie?= =?UTF-8?q?nt=20=E2=80=94=20github-script=20already=20declares=20`octokit`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `actions/github-script` injects an `octokit` binding, so `const octokit = ...` aborts the run with a SyntaxError before any logic executes. Caught on #4553's own merge, the first time the workflow ever ran; until this lands every merged PR carries one red check. The pre-merge `node --check` missed it because the test wrapper declared only {github, context, core, require} — a wrapper that omits an injected identifier cannot see a collision with it. The wrapper now carries the full set. --- .changeset/fix-cross-repo-closer-octokit.md | 23 +++++++++++++++++++ .github/workflows/cross-repo-issue-closer.yml | 8 +++---- 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-cross-repo-closer-octokit.md diff --git a/.changeset/fix-cross-repo-closer-octokit.md b/.changeset/fix-cross-repo-closer-octokit.md new file mode 100644 index 0000000000..2800e195f5 --- /dev/null +++ b/.changeset/fix-cross-repo-closer-octokit.md @@ -0,0 +1,23 @@ +--- +--- + +fix(ci): rename the cross-repo client — `github-script` already declares `octokit` + +Release-nothing: touches `.github/workflows/cross-repo-issue-closer.yml` only. + +`actions/github-script` injects an `octokit` binding into the script scope, so +the `const octokit = require('@actions/github').getOctokit(token)` added in +#4553 aborted the run before a single line of logic executed: + + SyntaxError: Identifier 'octokit' has already been declared + +This surfaced on #4553's own merge — the first time the workflow ever ran, and +the first time any of its runtime behaviour was exercised. Until this lands, +every merged pull request in this repository carries one red check. + +The pre-merge validation missed it for an instructive reason. The script was +syntax-checked against a hand-written wrapper declaring `{github, context, +core, require}` — and a wrapper that omits an injected identifier cannot +possibly see a collision with it. The check reported clean because it was +asking a narrower question than the runtime asks. The wrapper now carries the +full injected set, and the previous spelling fails it. diff --git a/.github/workflows/cross-repo-issue-closer.yml b/.github/workflows/cross-repo-issue-closer.yml index 8f6ebb957d..464c3ccdec 100644 --- a/.github/workflows/cross-repo-issue-closer.yml +++ b/.github/workflows/cross-repo-issue-closer.yml @@ -103,25 +103,25 @@ jobs: // A second client: `github` is bound to GITHUB_TOKEN, which has no // write access outside this repository. - const octokit = require('@actions/github').getOctokit(token); + const crossRepo = require('@actions/github').getOctokit(token); for (const [key, t] of targets) { try { - const { data: issue } = await octokit.rest.issues.get({ + const { data: issue } = await crossRepo.rest.issues.get({ owner: t.owner, repo: t.repo, issue_number: t.number, }); if (issue.state === 'closed') { core.info(`${key} is already closed — skipping.`); continue; } - await octokit.rest.issues.createComment({ + await crossRepo.rest.issues.createComment({ owner: t.owner, repo: t.repo, issue_number: t.number, body: `已由 ${thisRepo} 的 ${prUrl} 修复并合并。\n\n` + `(跨仓库的关闭关键字不会自动生效,本条由 \`cross-repo-issue-closer\` 工作流代为收口。)\n\n` + `---\n_Generated by [Claude Code](https://claude.ai/code)_`, }); - await octokit.rest.issues.update({ + await crossRepo.rest.issues.update({ owner: t.owner, repo: t.repo, issue_number: t.number, state: 'closed', state_reason: 'completed', }); From 4b35ff72290f35984472330c1a680dd3669b3a9f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 08:23:25 +0000 Subject: [PATCH 2/2] ci: log CROSS_REPO_ISSUE_TOKEN presence on every run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this the job returns early whenever a PR body carries no cross-repo reference — which is most PRs — so a repository that has the secret and one that does not produce byte-identical logs. Whether the credential is provisioned stays unanswerable until a cross-repo reference happens to show up, possibly days later. Presence only; the value is never read into the log. --- .github/workflows/cross-repo-issue-closer.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cross-repo-issue-closer.yml b/.github/workflows/cross-repo-issue-closer.yml index 464c3ccdec..dadaaf4282 100644 --- a/.github/workflows/cross-repo-issue-closer.yml +++ b/.github/workflows/cross-repo-issue-closer.yml @@ -60,6 +60,16 @@ jobs: 'gi', ); + // Report credential state on EVERY run, before any early return. + // Otherwise a repository with the secret and one without look + // identical until a cross-repo reference happens to show up — + // which can be days — and "is it configured?" stays unanswerable. + // Presence only; the value is never read into the log. + const token = process.env.CROSS_REPO_TOKEN; + core.info( + `CROSS_REPO_ISSUE_TOKEN: ${token ? 'configured' : 'ABSENT — cross-repo closes will be reported, not performed'}`, + ); + const targets = new Map(); for (const [, owner, repo, number] of body.matchAll(pattern)) { const key = `${owner}/${repo}#${number}`; @@ -75,8 +85,6 @@ jobs: } core.info(`Cross-repo targets: ${[...targets.keys()].join(', ')}`); - const token = process.env.CROSS_REPO_TOKEN; - if (!token) { // Degrade VISIBLY. Someone has to close these by hand, and this // comment is the only thing that will tell them so.