Repository navigation
cross-repo-issue-closer.yml: a refused close in the per-target loop leaves the foreign issue open and the job green #9595
Copy link
Copy link
Closed
Labels
Description
Activity
- addedbugSomething isn't workingSomething isn't workingand removed
on Aug 18, 2026 Claiming this card.
- session:
session_01XqDQYVU5smx29ts9pAErja - branch:
claude/issue-9595-cross-repo-closer-outcome - worktree:
../objectstack-9595, branched fromorigin/main@e8dba8a8a(which contains PR fix(ci): the cross-repo hand-close notice survives a refused post, and a lost one fails the job #9594)
Declared file surface for this claim:
.github/workflows/cross-repo-issue-closer.yml, plus a.changeset/*.mdand whatever test/self-test surface the H2 measurement lands in (expected underscripts/+ its patrol test). Will amend this list in a follow-up comment if the surface grows.Generated by Claude Code
Generated by Claude Code
- session:
{ "issue": 9595, "status": "done", "branch": "claude/issue-9595-cross-repo-closer-outcome", "pr": "https://github.com/objectstack-ai/objectstack/pull/9645", "premise_still_valid": true, "summary": "The per-target loop now records the keys it could not close, still runs to the end (the catch is unchanged, per ruling 1), and after the loop writes a job summary listing them with reasons and calls core.setFailed naming them. describe(error) was hoisted once rather than hand-copied a second time, which is the only edit touching PR #9594's lines and is named in the PR body. All three red/green facts were re-derived, not inherited: the job's context is absent from the LIVE ruleset (read via GET /rulesets/12119582 - six required contexts, none of them this job), the trigger is still pull_request_target:[closed] + merged==true, and the repo's only two workflow_run: listeners watch CI and Release. Two deviations for PM adjudication. (a) Ruling 4 said 'ship a .changeset/*.md'; this PR publishes nothing and carries skip-changeset instead, matching PR #9594 - an empty-frontmatter changeset is not an option because check-empty-changeset.mjs rejects newly added ones. (b) The gate is wired into lint.yml as 'node scripts/...' rather than a pnpm check:* alias, following PR #9620's precedent, because root package.json is declared territory of epic #9465 while it runs. Also noting: origin/main advanced from e8dba8a8a to 5f99739cb during the work; the branch is based on e8dba8a8a and the only overlap with an open PR is lint.yml (#9620 inserts at line ~270, this inserts at ~786).", "tests": "All run after the final commit 0a7e6c32d, on that tree. Local gate union derived with node scripts/pm/dispatch-gates.mjs over the three changed paths, plus check:nul-bytes for any edit: check:node-version OK (28 setup-node steps / 25 workflows); check:required-contexts self-test + gate OK; check:shard-attestation self-test 92 assertions + gate OK; check:workflow-status-functions self-test 34 assertions + gate OK (25 workflows, 44 jobs, 24 job-level if:); check:type-check-coverage self-test 109 cases + gate OK; check:nul-bytes self-test 75 assertions + gate OK (6174 files, no raw control bytes) plus a direct grep -naP over the three changed files (no hits). New gate, both halves: 'node scripts/check-cross-repo-closer-outcome.mjs --self-test' -> 39 assertions, 7 mutations of the shipped script each driven to red; 'node scripts/check-cross-repo-closer-outcome.mjs' -> OK, 52 assertions over 10 scenarios driving the 8734-char script extracted from the YAML. REVERSE VERIFICATION (from the committed state, against the pre-fix script taken from origin/main, working tree untouched): 9 of 52 assertions fail, all in L3/L4/L5 - 'L3 FAILS the job -- the defect this card is about; got 0', 'L4 setFailed names every target left open', 'L5 still fails the job when the richer channel is gone', and six more. P0/P1/N1/N2/N3/L1/L2 pass identically on both, which is the assertion that the notice path (#9594) and the isolation are untouched. Direction note, honest: L3's warning assertion also flips for a second reason - the warning text now routes through the hoisted describe, so it names HTTP 404 where it previously named only the bare message. NOT RUN LOCALLY: check:type-check-debt. It is derived only because its source names lint.yml, it is the --re-measure variant that re-runs tsc across 13 ledger packages (farm-scale), and the shared /tmp/os-heavy-verify.lock was held throughout by another agent's @object-ui/console build from /home/user/objectstack-8134 (flock -w 3000, PID 28692) with five runs queued behind it. Two acquisition attempts, ~15 min, both exit 99. The non-re-measure half of the same script passed, and this diff adds no TypeScript, touches no tsconfig and changes no typecheck script. No ablation applies (no dist/ involved).", "open_questions": [], "out_of_scope_findings": [ "filed as #9642: check-required-contexts.mjs reasons from 'no agent seat can read the ruleset (403)' - measured false. The 403 is for the classic branch-protection endpoint, which this repo does not use; GET /rulesets and /rulesets/{id} answer 200 from an ordinary agent seat, so the gate CAN verify the required SET it says it cannot (the #5617 half with no signal today). Premise is quoted in three places (script header line 79, prescription line 855, lint.yml step comment).", "filed as #9643: cross-repo-issue-closer.yml skips an already-closed foreign issue WHOLE, so it never gets the PR backlink - which the file's own header names as half the defect the workflow exists to fix. This is the third silent exit path H3 asked about; it carries a judgment (three defensible behaviours) so it is a card rather than a rider. Scenario L2 in the new harness pins today's behaviour.", "not filed, recorded: a measurement posted as a comment on #9533 (open, needs-user-decision). Its question - was the maintainer's 2026-08-18 six-item required-set reading complete, or partial? - is answered by the live ruleset read above: complete. Build Docs and Console Pin Gate are genuinely absent from the ruleset, and only one ruleset exists.", "not filed, examined and rejected as this file's defect: missing timeout-minutes: (a repo-wide pattern - 14 of 25 workflows declare none) and missing concurrency: (parallel merges producing parallel runs is correct here, each run handling its own PR's targets)." ] }Supporting measurements for the H1/H4 claims, since they are the load-bearing new numbers:
- H1 (widened from the card's 489): 1176 merged PRs scanned with the workflow's own regex — 0 carry a qualified foreign closing keyword; 25 carry the qualified same-repo spelling, which the skip branch handles and GitHub did close. The loop has had nothing to do in that entire window, so its happy path was checked by running it under doubles rather than by trusting it. No second defect found in the parse, the key format or the close call.
- H4 (a red here does reach someone): this job has 3 failures in 2281 runs. Two, on 2026-08-02 at 07:56Z and 08:12Z, were
SyntaxError: Identifier 'octokit' has already been declared; the fix merged at 08:35Z, 39 minutes after the first, and the second red had landed on an unrelated author's merge. The channels are GitHub's own — run conclusion, a red check on the merged PR, and the default Actions failure notification to the triggering actor, who is a real person here (last 100 runs: os-steve 38, os-zhuang 30, os-project-manager 10, os-support-ai 8, os-sam 6, hotlong 5). Caveat stated rather than hidden: no repo-side patrol sweeps failed runs — nothing inscripts/pm/**reads run conclusions and no round report enumerates reds — so the signal is GitHub's, not this repo's. - H2: the harness PR fix(ci): the cross-repo hand-close notice survives a refused post, and a lost one fails the job #9594 built and discarded is now
scripts/check-cross-repo-closer-outcome.mjs, wired intolint.yml, running the SHIPPED bytes (parsed out of the YAML, never retyped) as oneAsyncFunctionbody — including a compile assertion, which is the 2026-08-02 failure class nothing in CI catches today.dispatch-gates.mjsdiscovers it automatically (107 families ⇢ 108).
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 18, 2026 - added a commit that references this issue
on Aug 23, 2026
Filed unassigned by the
domain:devxos-dev seat while landing #9575 / PR #9594. Recording, not claiming. Dedup-searched (221 open issues, workflow name + phrasing): the only other card naming this file is #9575 itself, whose scope is the other call site.The finding
.github/workflows/cross-repo-issue-closer.yml's per-target loop is guarded, and its guard is the right shape for the isolation it buys — one unreachable target must not take the rest down. But the isolation and the job's OUTCOME are separable, and today only the first exists:The comment states the requirement — "a failure here must not read as success" — and the code does not meet it.
core.warningleaves the job green. So when the close of a foreign issue is refused, the issue stays open, the PR carries no notice, and the job's conclusion is identical to the ~2268 runs where there was nothing to do at all. That is the same silence #9373 / #9424 / #9575 are about, one layer in.Why this half is the LIVE one
#9575's notice path only runs while
CROSS_REPO_ISSUE_TOKENis missing, and sampled job logs show it configured on 2026-08-04, 08-08, 08-12, 08-16 and 08-18 — so that branch is latent. This loop is the branch that actually runs whenever a merged PR carries a qualified cross-repo closing keyword. It has not fired recently (the 489 most recent merged PR bodies contain zero such keywords), but when it does fire it fires on exactly the occasion the workflow exists for.The shape a fix would take, if triage wants one
Not a mechanical edit, which is why this is a card and not a rider on PR #9594:
catchexactly as it is — the loop must still run to the end;core.setFailednaming them, the waymerge-queue-triage.ymldoes since merge-queue-triage.yml has the same untolerated comment-post as #9373 — and here the lost comment IS the flake evidence #9424;pull_request_target: [closed]so it gates nothing already merged, noworkflow_run:listener), so it is likely to land the same way — but it should be re-derived, not inherited.PR #9594 deliberately left this loop untouched: its dispatch scoped it to the unguarded notice, and the step-level
retries:it adds only changes how many times a target is asked, never whether a spent failure is reported.Ref: #9373 · PR #9423 · #9424 · PR #9574 · #9575 · PR #9594
Generated by Claude Code
Generated by Claude Code