Repository navigation
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
Activity
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsTriage:
pm:queue· Task ·tooling. Port the framework-hardenedcross-repo-issue-closer.ymlfamily rather than re-deriving it: PR-guard placed before the already-closed branch, refusal accounting (setFailedafter the loop), backlink-on-closed withstate_reasonhandling 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
os-support-ai commented
on Aug 19, 2026 CollaboratorMore actionsClaim: 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 underscripts/(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 inpackages/plugin-view(#5270),apps/console(#5286),eslint-rules/(#5191), andcontent/docs/**(#5271).⚠️ Note PR #5207 is open onpackages/plugin-dashboardand unrelated to this surface.分诊的串行注记已现读,并且它现在过期了 —— 朝好的方向。 注记(2026-08-18 23:15Z)写
objectstack#9711 是 in flight,移植 post-#9711 的版本。实测:objectstack#9711 已于 2026-08-19T00:23:33Z 关闭为completed(closed_byos-steve)。所以 post-#9711 的版本已经在 framework main 上,这张卡不再被挡,dev 直接照 framework 现状移植即可 —— ⛔ 但仍要以 frameworkorigin/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
os-support-ai commented
on Aug 19, 2026 CollaboratorMore actions{ "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—extractScriptneeds a real YAML parser rather than a hand-rolled one.yaml@2.9.0was 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 sameyaml: ^2.9.0at its root.
Generated by Claude Code
Filed unassigned by the
domain:devxos-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.ymlin 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: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.
GET /repos/{owner}/{repo}/issues/{N}answers for a PR number withstate,state_reasonand the rest, plus apull_requestkey nothing here reads. Measured on the framework repo today:issues/9716(a PR) answerspull_request: { url, html_url, diff_url, patch_url, merged_at };issues/9711(an issue) has no such key. Since this workflow'sthisRepoisobjectstack-ai/objectui, the targets it acts on are in framework — so a merged PR here whose body saysFixes objectstack-ai/objectstack#9716would 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.catchis a barecore.warning, so an expired or under-scopedCROSS_REPO_ISSUE_TOKENleaves 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,setFailedafter the loop, keep the isolation.)state_reasonhandling and a per-PR marker for re-run idempotency.)scripts/check-cross-repo-closer-outcome.mjs, which extracts the inline script out of the YAML and drives it under doubles, plus a--self-testthat 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 thestate === 'closed'branch (a merged PR reads asstate: 'closed'withstate_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