Skip to content

cross-repo-issue-closer.yml here is the pre-hardening vintage of framework's copy: it closes a foreign PULL REQUEST, reports refusals as green, and has no harness #5261

Description

@os-steve

Filed unassigned by the domain:devx os-dev seat while landing objectstack-ai/objectstack#9711 (which fixes the first item below in the framework repo's copy of this same workflow). Recording, not claiming. Dedup-searched over the 229 open issues in this repo (workflow name, "closing keyword", the Chinese phrasing): no card covers this.

What is here

.github/workflows/cross-repo-issue-closer.yml in this repo is a copy of the framework repo's workflow, forked before four rounds of hardening landed there. Its per-target loop today is:

const { data: issue } = await github.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 github.rest.issues.createComment({ ... });   // the backlink
await github.rest.issues.update({ ... state: 'closed', state_reason: 'completed' });

Four defects, in descending severity. The first is the one being fixed in the framework repo right now, and it is the reason this card exists rather than a note in that PR — the fix has to land in the repo that owns the file.

  1. A closing keyword aimed at a foreign PULL REQUEST closes that PR. Every pull request is also an issue to this endpoint: GET /repos/{owner}/{repo}/issues/{N} answers for a PR number with state, state_reason and the rest, plus a pull_request key nothing here reads. Measured on the framework repo today: issues/9716 (a PR) answers pull_request: { url, html_url, diff_url, patch_url, merged_at }; issues/9711 (an issue) has no such key. Since this workflow's thisRepo is objectstack-ai/objectui, the targets it acts on are in framework — so a merged PR here whose body says Fixes objectstack-ai/objectstack#9716 would comment on that pull request and close it. GitHub's own closing-keyword parser never closes a pull request, and a wrongly-closed PR loses its merge-queue membership and any armed auto-merge in the same step; neither comes back by itself.
  2. A refused close reports green. The catch is a bare core.warning, so an expired or under-scoped CROSS_REPO_ISSUE_TOKEN leaves every target open and hands the run the same conclusion as a run with nothing to do. (Fixed in framework by cross-repo-issue-closer.yml: a refused close in the per-target loop leaves the foreign issue open and the job green objectstack#9595 / PR fix(ci): a refused cross-repo close fails the job instead of passing as a warning objectstack#9645: record the failures, setFailed after the loop, keep the isolation.)
  3. An already-closed target is skipped whole, so it never gets the backlink — the half of the deliverable that is not redundant. (Fixed in framework by cross-repo-issue-closer.yml: an already-closed foreign issue is skipped whole, so it never gets the backlink the workflow exists to leave objectstack#9643 / PR fix(ci): an already-closed cross-repo issue gets the PR backlink instead of being skipped whole objectstack#9716, together with state_reason handling and a per-PR marker for re-run idempotency.)
  4. Nothing here has ever been executed under test. framework has scripts/check-cross-repo-closer-outcome.mjs, which extracts the inline script out of the YAML and drives it under doubles, plus a --self-test that mutates the script and requires the battery to go red. This repo has no equivalent, and the inline script is around 100 lines of code that only runs after a merge.

Severity, measured rather than assumed

Low so far, and worth stating plainly: this branch may never have had a target. In the framework repo the equivalent measurement is 0 qualified foreign closing keywords across three independent windows of the 1176 most recently merged PRs, and 0 of 1129 distinct bare-form closing-keyword targets naming a pull request number. The same measurement has not been run over this repo's merged PRs — doing that is the first step of whoever takes this card, because it decides whether item 1 is urgent or merely wrong.

Suggested shape

Port the framework file rather than re-deriving it: the guard is if (issue.pull_request) { announce and refuse } placed before the state === 'closed' branch (a merged PR reads as state: 'closed' with state_reason: null, so a guard after it still comments on someone else's pull request), and the harness comes with the scenarios that pin it. Whether this repo wants the whole family or only item 1 is a triage decision.

Ref: objectstack-ai/objectstack#9711 · objectstack-ai/objectstack#9643 · objectstack-ai/objectstack#9595 · objectstack-ai/objectstack#9575

Activity

  1. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Triage: pm:queue · Task · tooling. Port the framework-hardened cross-repo-issue-closer.yml family rather than re-deriving it: PR-guard placed before the already-closed branch, refusal accounting (setFailed after the loop), backlink-on-closed with state_reason handling and re-run idempotency, and the extracted-script harness with --self-test. First deliverable is the measurement over THIS repo's merged PRs (it decides whether item 1 is urgent or merely wrong), as the card states. Serial note for the claimer: objectstack#9711 is in flight on the framework copy — port the post-#9711 vintage, and name that PR in the claim's serial-constraints line.


    Generated by Claude Code

  2. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 13
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5261-port-hardened-cross-repo-closer
    Worktree: objectui-issue-5261
    Domain: repo:objectui (tooling / CI)
    File surface: .github/workflows/cross-repo-issue-closer.yml + the extracted script and its harness under scripts/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus
    Clause-②: no — CI-time only; no published surface, no accept-set change.
    Serial constraints cleared: .github/workflows/ is free — #5246 landed (PR5273) and nothing else is in flight there. The four other in-flight cards are in packages/plugin-view (#5270), apps/console (#5286), eslint-rules/ (#5191), and content/docs/** (#5271). ⚠️ Note PR #5207 is open on packages/plugin-dashboard and unrelated to this surface.

    分诊的串行注记已现读,并且它现在过期了 —— 朝好的方向。 注记(2026-08-18 23:15Z)写 objectstack#9711 是 in flight,移植 post-#9711 的版本。实测:objectstack#9711 已于 2026-08-19T00:23:33Z 关闭为 completed(closed_by os-steve)。所以 post-#9711 的版本已经在 framework main 上,这张卡不再被挡,dev 直接照 framework 现状移植即可 —— ⛔ 但仍要以 framework origin/main 的实际文件为准,不要照 #9711 正文里的代码片段抄(那是提案时的写法)。

    #9711 还提供了本卡用得上的两件事:PR-guard 的准确形状(if (issue.pull_request) { announce and skip }),以及 framework 侧的严重度量测 —— 1176 个最近合并的 PR 里,零个 qualified foreign closing keyword(2026-08-18 两个不同窗口各测一次,都是零)。⚠️ 那是 framework 的数字,不是本仓的;triage 把「先在本仓跑同一个量测」定为第一交付物,正是因为它决定 item 1 是紧急还是仅仅错误。⛔ 不要把 framework 的零搬过来当本仓的读数。


    Generated by Claude Code

  3. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator
    {
      "issue": 5261,
      "status": "done",
      "branch": "claude/issue-5261-port-hardened-cross-repo-closer",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5295",
      "premise_still_valid": true,
      "summary": "Ported the framework-hardened cross-repo-issue-closer family from objectstack-ai/objectstack cb9ff23fa (repo head a065e4655) rather than re-deriving it: the issue.pull_request guard placed BEFORE the already-closed branch, refusal accounting (setFailed after the loop, the catch's isolation unchanged), backlink-on-closed split three ways by state_reason with a per-PR marker for re-run idempotency, plus the extracted-script harness and its --self-test, wired into lint.yml. FIRST DELIVERABLE, the measurement over THIS repo, and it is NOT the framework's zero: over all 3425 merged PRs here (#1..#5288, the closed-PR listing paged to exhaustion at 37 pages), 60 carry a qualified foreign closing keyword (61 occurrences, 59 distinct targets) against the framework's 0-of-1176; of the 57 targets that resolve, ZERO name a pull request (2 unresolvable: objectstack-ai/cloud#667 and #654, this session's proxy answers 403 for that repo and add_repo reports no access; 5 more resolve only via the objectstack-ai/framework and objectql/objectui renames and are all issues). Counter-probes so those zeros are readings: 246 qualified same-repo references and the bare form in 972 of the same PRs. The severity finding is defect 3, not defect 1: since this workflow landed here on 2026-08-09 (#3969) 15 merged PRs carried a foreign target, and in 14 of the 15 the framework issue was closed by a seat 1-2 seconds after the merge, BEFORE the workflow run was even created (the 15th's close lands in the same second the run is created, runner later still) - so the loop hit state === 'closed' 15 times out of 15, skipped each target whole, left no backlink on any of them, and reported success, as have all 941 runs of this job. Quiet/warn/red judgement: RED, matching the framework's landed version (warning annotation naming the target plus a post-loop setFailed carrying the malformed class separately from the refusal class); convergence is the card's binding scope, and this repo's own facts support it more strongly than the framework's did - affordability re-derived here rather than inherited: the main ruleset (11776024) has no required_status_checks rule at all, the job runs only after the merge, and this repo has no workflow_run listener.",
      "tests": "All at HEAD a5a0678ac (the commit the PR is opened on). EXECUTED, green: `node scripts/check-cross-repo-closer-outcome.mjs --self-test` -> '77 assertions, 14 mutations of the shipped script each driven to red'; `node scripts/check-cross-repo-closer-outcome.mjs` -> 'OK (105 assertions over 18 scenarios, driving the 18420-char script extracted from .github/workflows/cross-repo-issue-closer.yml)'. REVERSE VERIFICATION (the refusal-accounting proof the bar asks for): the module is inert on import by design, so judge() was pointed at a tree holding this repo's origin/main copy of the workflow - same 105 checks produce 40 failed assertions there and 0 here, including '[L3] L3 FAILS the job -- the defect this card is about; got 0' (L3 simulates one target's issues.get refused with HTTP 404 while the other two stay reachable: pre-port calls setFailed zero times, ported goes red and still closes the other two) and '[L12] L12 still closes the other two targets -- isolation holds; got 3' (pre-port closed all three, one of them a pull request). No rebuild is involved in either leg - the harness extracts the shipped YAML bytes and runs them as an AsyncFunction, there is no dist/ in the path. Also executed green: check-control-bytes, check-changeset-presence (4 files changed, 0 under a released package src/, exits 0 - no changeset owed, and objectui has no skip-changeset label so none was applied), check-changeset-no-major, check-lint-coverage, check-type-check-coverage, check-node-esm-load, check-phantom-dependencies, check-action-forward-parity, check-published-dist-tooling, check-skills-paths, `npx eslint scripts/check-cross-repo-closer-outcome.mjs`, and `vitest run --project unit scripts/__tests__` = 55 files / 1272 tests passing (includes the four that pin these workflows: ci-cd-pipeline-doc, lint-workflow, merge-queue-reporting, dependabot-merge-gate). YAML validity and the credential walk were machine-diffed old vs new: trigger, top-level permissions, absence of job permissions, merged==true gate, action version, github-token expression and step env all SAME; added only the step inputs retries:3 and retry-exempt-status-codes:400,401,404,422, and one new read-only API method (issues.listComments) on a repo the token already comments on and closes in. NOT executed and not claimed: GitHub Actions cannot run locally, so nothing here asserts a real run of the workflow. objectui has no scripts/pm/dispatch-gates.mjs (framework-only), so the gate set was derived by hand from the changed paths.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectstack-ai/objectstack#9917 (label `finding`, unassigned): the framework's own copy of this family carries two wrong/stale evidence citations - its harness cites objectui#4478 as a closed ISSUE answering state_reason null when that number is a PULL REQUEST (which is why it answers null, and which is the very thing L13 refuses), and its lint.yml step comment hand-counts 'seven ways' / 'Ten scenarios' where MUTATIONS now holds 14 and SCENARIOS 18. Comments only, no behaviour at risk; the port deliberately dropped both counts rather than carrying them across."
      ]
    }

    File surface, wider than the card declared

    The card scoped this to the workflow plus the script and harness under scripts/. Two additions were needed and are in the PR:

    • .github/workflows/lint.yml — the wiring. Without it the harness runs nowhere, and the self-test asserts the wiring is present (it was red on exactly those two assertions until the step landed).
    • package.json + pnpm-lock.yaml — extractScript needs a real YAML parser rather than a hand-rolled one. yaml@2.9.0 was already resolved in this lockfile as a transitive, so the delta is three lines and no new package version enters the tree; the framework declares the same yaml: ^2.9.0 at its root.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions