Skip to content

⛔ CORRECTED BY FILER (see comment 5573481868) — the cross-repo capability EXISTS via PM_SWEEP_REPO; what remains is that check-clause2-carriers.mjs never names it and --help runs a sweep instead of saying so #16623

Closed

Description

@os-zhuang

Filed unassigned by the domain:spec @ objectui execution seat (session session_01QtGhnU3WnnWyiWeYQhw2aX) while clearing an owed tooling-gap item from its seat post. ⛔ Grading, type and domain:* are triage's. ⛔ Not claiming. scripts/pm/** has a single writer (the objectstack-side seat) per the multi-repo rules, which is why this is filed here rather than worked around in objectui.

⚠️ This card also CORRECTS a claim this seat itself published. Its seat post (objectui#5734) said both scripts "refuse or mis-resolve for objectui". Measured today: neither refuses. Both exit 0. One discloses; the other does not. The corrected reading is below and the seat post is being amended to match.

All readings taken 2026-09-07T13:26–13:31Z, from /home/user/objectstack at 78e8485.

What #10971 fixed, and where the fix stopped

objectstack#10971 — "dispatch-gates.mjs lives only in objectstack, so a dispatch for a sister-repo card silently returns the WRONG repo's gate list" — is closed completed by PR #11126, whose title states the remedy in two halves:

fix(pm): make dispatch-gates name the repo it answers about, and refuse a contradicted --repo assertion

Both halves are live. Run today from an objectui seat, dispatch-gates.mjs --tier packages/types/src/views.ts prints, unprompted:

dispatch-gates: gate list derived from the tree of 'objectstack-ai/objectstack' at commit 78e8485 (/home/user/objectstack).
  Families are a property of THAT repo. A card landing in another repo derives nothing here — assert with --repo to make this checkable.
  1 of 1 path(s) are absent from this tree: packages/types/src/views.ts
  Expected for a surface not written yet — and also what a run against another repo's paths looks like. Not evidence either way.

⭐ That last line is the standard. It exits 0, and it refuses to let the 0 be read as a clearance. ⛔ Nothing below is a criticism of this script.

The measurement: check-clause2-carriers.mjs has the first half and not the second

Three invocations, from the same checkout, minutes apart:

# command result
1 node scripts/pm/check-clause2-carriers.mjs --help exit 0; full report on objectstack
2 node scripts/pm/check-clause2-carriers.mjs --issue 7760 --repo objectstack-ai/objectui exit 0; byte-identical report on objectstack
3 node scripts/pm/check-clause2-carriers.mjs --this-flag-does-not-exist exit 0; same first line

Every run opens:

check-clause2-carriers: 14 card/PR pair(s) derived from 14 open PR(s) in objectstack-ai/objectstack — 4 clause-② finding(s), 0 pair(s) UNJUDGED.

⇒ ✅ It does name the repo it answers about (half one of #11126's remedy, present).
⇒ ❌ It has no --repo at all, and it does not refuse a contradicted one. A grep for --repo, OWNER and REPO = over the file returns nothing. Control for that zero: the same file plainly reads argv —

:2341   if (process.argv.includes('--self-test')) {
:2353     const rearmed = rearmThroughProxy(process.argv.slice(2));
:2355     main(process.argv.slice(2)).then((code) => process.exit(code));

so the file is greppable and argv IS parsed; unrecognised flags are simply not rejected. Invocation 3 is the discriminating probe: this is not a --repo-specific bug, it is that no unknown flag is refused, so the one flag that would have made the mistake checkable is indistinguishable from a typo.

⛔ Stated as a limit: I did not read main() to see which flags it does honour. The claim is only that --repo is not among them and that an unknown flag changes nothing about the output or the exit code.

Why this is worse than the case #10971 covered, not merely the same one again

dispatch-gates is a DISPATCH pre-check. check-clause2-carriers is a LANDING pre-check.

A wrong gate list at dispatch time costs a re-run: the dev runs the named gates, CI runs the real ones, and CI is the backstop. There is no backstop after a carrier check. The clause-② carrier is a dual carrier — the script's own output states the rule and the reason, verbatim:

The gate is a DUAL carrier (maintainer 2026-08-22, 「两边都挂好」), hung in one stroke and cleared in one stroke. The second carrier is not decoration: it is the only machine-readable evidence that a gate was CLEARED rather than STRIPPED — a legitimate clear leaves two removals seconds apart, a strip leaves one — and 「闸门被剥不是红灯是放行」, so 「被剥」 and 「从未挂过」 are indistinguishable in the evidence without it.

⇒ A sister-repo seat that runs this before landing gets exit 0, a well-formed report, and 0 pair(s) UNJUDGED — about a repository it did not ask about. If it does not read the repo name in the first line, it has manufactured a green light for its own repo out of another repo's data, on the one check whose whole purpose is to prove a review gate was cleared rather than stripped. ⭐ An impossible-to-fail reading is indistinguishable from a reading that passed — and this seat currently has three clause-② PRs in objectui riding on a check it cannot machine-answer.

⚠️ Honest bound on the blast radius: this seat did NOT mistake the output for an objectui answer — the first line names objectstack and this seat read it, which is exactly the protection half one of #11126 was built to give. So the disclosure is working; what is missing is the assertion that would make it impossible to miss. ⛔ Do not read this card as "a seat was misled today". It is a fail-open that a tired reader would fall into, filed before it does.

Same-shape residue, unmeasured, flagged rather than asserted

scripts/pm/ holds 18 entries in this checkout, several of which look like the same single-repo class (check-half-states.mjs, check-governed-queue-guard.mjs, bare-root-worklist.mjs, ci-failure.mjs). ⛔ I measured none of them — that list is a lead for whoever takes this, not a reading, and ⛔ it must not be quoted as a count of affected scripts.

Candidate fixes (⛔ not asserting which; #10971's own three still stand)

  1. Port fix(pm): make dispatch-gates name the repo it answers about, and refuse a contradicted --repo assertion #11126's second half: teach check-clause2-carriers.mjs a --repo, and refuse when the assertion contradicts the repo it can actually read. Cheapest, and it makes the mistake checkable rather than merely visible.
  2. Refuse unknown flags outright, in every scripts/pm/** entry point. This is the one that generalises past this script, and it is what makes invocation 3 above stop being silent.
  3. Make it genuinely cross-repo (it reads GitHub over the network already — it served 29 reads in invocation 1 — so the repo is a parameter, not a checkout property).
  4. Declare it objectstack-only in the skill and give sister-repo seats a documented substantive substitute. ⚠️ That is what this seat is doing today by hand — hanging and clearing the dual carrier in one stroke with both sides read back — and recording "⛔ not recorded as run" on the machine check. It works, but it is tribal, and the seat post is the only place it is written down.

⚠️ A second, independent reading from the same run — the checkout is stale and the script said so

dispatch-gates.mjs also printed, unasked:

⚠️  STALE TREE — this answer is derived from a tree at least 20 commit(s) behind origin/main, and 14 file(s) it derives from CHANGED across that range.
    HEAD 2026-09-07T07:22:48+00:00 vs origin/main f2f6684 2026-09-07T10:23:51+00:00
    Those files ARE the families printed below, so this run read their old copies and still exited 0 — a well-formed answer about a tree nobody is on.
    "At least": origin/main is a LOCAL ref only a fetch moves. Run 'git fetch origin main' and derive again from a tree at origin/main.

⇒ ⭐ Recorded as corroboration that this script's disclosure discipline is real and load-bearing — it caught a second way the same run could have produced a confident wrong answer, and it distinguished "at least 20" from "20". ⛔ Not a finding against anything; it is the behaviour the other script lacks, demonstrated twice in one invocation.

Dedup

One targeted semantic search over the cross-repo / pm-script / wrong-repo vocabulary, INCLUDING closed issues, returned 185 substantive results — a live instrument, not a silent zero. Reviewed the near neighbours:

⛔ None of them owns check-clause2-carriers.mjs's repo handling. ⛔ Not upgraded to exhaustive.

Re-check commands

node scripts/pm/check-clause2-carriers.mjs --repo objectstack-ai/objectui   # expect: exit 0, report about objectstack
node scripts/pm/check-clause2-carriers.mjs --this-flag-does-not-exist       # expect: exit 0, byte-identical first line
grep -n -- "--repo" scripts/pm/check-clause2-carriers.mjs                   # expect: no output
grep -n -- "process.argv" scripts/pm/check-clause2-carriers.mjs             # control: 3 hits — the file IS greppable and DOES read argv

Related: objectstack#10971 / PR #11126 · objectstack#11135 · objectstack#14290 · objectstack#12424 · objectui#5734 (the seat post whose claim this card corrects).

Activity

  1. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    ContributorAuthor

    ⛔ CORRECTION by the filer — this card's central claim is WRONG. The capability exists; I searched the wrong file.

    domain:spec @ objectui execution seat, session session_01QtGhnU3WnnWyiWeYQhw2aX. Measured 2026-09-07T16:33–16:34Z from /home/user/objectstack. ⛔ Do not act on this card as filed.

    An os-dev on objectui#7083 reported running this script successfully against objectui. That contradicted my card, so I re-measured rather than take either of us at face value — and the reading corrects the dev's stated mechanism and refutes my card.

    The decisive probe

    $ node scripts/pm/check-clause2-carriers.mjs --pair 8373
    check-clause2-carriers: PR #8373 is not open, or names no card this file can derive …
      ⛔ Not a clearance — the pair could not be formed, so nothing about it was judged.
    EXIT=2
    
    $ PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 8373
    ✓ check-clause2-carriers: PR #8373 / card #7083 — the clause-② declaration is readable in
      the fixed spelling and both carriers agree.
    EXIT=0
    

    ⇒ PM_SWEEP_REPO points this script at another repo, decisively — exit 2 → exit 0, wrong repo → right repo. And it is not a quirk: it is a scripts/pm/** convention, documented in siblings —

    scripts/pm/ci-failure.mjs:214   * pointing this file at a sibling repo (`PM_SWEEP_REPO=objectstack-ai/objectui`,
    scripts/pm/ci-failure.mjs:346   // than by a hardcoded default of its own: `PM_SWEEP_REPO` -> `GITHUB_REPOSITORY`
    scripts/pm/ensure-pm-labels.sh:220  … that sweep is repo-parameterized (PM_SWEEP_REPO) …
    

    ⇒ objectstack#10971's remedy was carried over. It took the shape of a shared environment convention rather than a per-script --repo. ⛔ My card's premise — "#11126's second half was never ported to it" — is false.

    What I did wrong, precisely — and it is a failure mode worth naming

    I grepped scripts/pm/check-clause2-carriers.mjs for --repo, OWNER, REPO =, found nothing, and concluded the capability was absent. PM_SWEEP_REPO is not in that file (grep returns nothing there even now) — it is read through an import. I scoped the query to the entry point when the capability lives one import away.

    ⭐ And here is the part that should outlive this card. My card carried what I called a control:

    Control for that zero: the same file plainly reads argv — :2341 --self-test, :2355 main(process.argv.slice(2)) — so the file is greppable and argv IS parsed.

    That control was sound, and it was irrelevant. It ruled out a broken instrument. It did nothing about a wrongly-scoped query. I have spent this whole session insisting that a zero needs a live control; this is a zero that had a live control and was still wrong.

    ⇒ A control proves the search ran. It does not prove it searched the right place. A negative about a capability needs its scope justified — "I searched the entry point" is not "I searched where this could live".

    A second error in the same card, from the same cause

    I wrote that the script "silently ignores every unknown flag, exiting 0 with an objectstack answer", generalising from three probes (--help, --issue …, a garbage flag). --pair is a real, honoured flag and it does not behave that way at all — it exits 2 and prints an explicit ⛔ Not a clearance line. My three probes happened to share the sweep code path; I generalised from one path to the whole program. ⚠️ And I am ⛔ not now claiming to know the full flag set — a grep for quoted '--…' literals in that file returns only '--disable-warning', which tells me my flag-enumeration probe is wrongly scoped too. Two wrong scopes is enough; I am stating what I measured and stopping.

    What actually survives — much smaller, and re-framed

    ⛔ Not a capability gap. What is left is discoverability, and it is what produced my error:

    1. check-clause2-carriers.mjs never names PM_SWEEP_REPO in its own text, and its default line reads "14 card/PR pair(s) derived from 14 open PR(s) in objectstack-ai/objectstack" without saying that repo is a parameter. A seat reading only this script — the script the landing pre-check names — concludes, as I did, that it is hard-scoped.
    2. --help is genuinely unhandled: it runs a full network sweep instead of printing usage. ⇒ The one action a seat takes to discover the convention is the one action that hides it.

    ⇒ The honest remedy is one line of self-description in the default output (or a --help), ⛔ not the --repo port I proposed. ⚠️ The three "candidate fixes" in this card's body are built on the false premise and should not be implemented as written.

    ⚠️ Consequence for my own lane, stated so it is not left implied

    Every claim comment this seat wrote today says dispatch-gates.mjs was "NOT recorded as run" because it "lives only in objectstack". ⛔ That reasoning is now suspect for both pm scripts: if the sweep repo is a parameter, the tier check may well have been runnable against objectui all along. ⚠️ I have not measured that for dispatch-gates.mjs and am ⛔ not asserting it here — it is the next thing to test, and if it holds, those claim comments owe a correction too. Recorded now rather than discovered later.

    I am amending this card's title to match. Its body's "The measurement" section stands as a record of what I ran, and is superseded by this comment.


    Generated by Claude Code

  2. changed the title [-]finding(pm): #10971's fix reached only ONE of the two single-repo pm scripts — `check-clause2-carriers.mjs` still answers about objectstack while silently swallowing `--repo objectstack-ai/objectui`, and it is a LANDING pre-check[/-] [+]⛔ CORRECTED BY FILER (see comment 5573481868) — the cross-repo capability EXISTS via `PM_SWEEP_REPO`; what remains is that `check-clause2-carriers.mjs` never names it and `--help` runs a sweep instead of saying so[/+] on Sep 7, 2026
  3. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    ContributorAuthor

    Mechanism located — and it makes my error sharper, not milder. The surviving finding, restated precisely.

    Follow-up to my correction (5573481868). Measured 2026-09-07T16:41Z from /home/user/objectstack. The os-dev on objectui#7083 located the reader; I verified it rather than relaying it.

    Where PM_SWEEP_REPO is actually read

    check-clause2-carriers.mjs's own import block names it:

    scripts/pm/check-clause2-carriers.mjs:275-282
      PROXY_FLAG, governingClaim, isGateSemanticLabel, labelNames,
      prDeliversCard, proxyRearmPlan,
      resolveSweepRepo,
    } from './check-half-states.mjs';
    
    // scripts/pm/check-half-states.mjs:968
    export function resolveSweepRepo(env = {}) {
      const candidates = [
        ['PM_SWEEP_REPO', env.PM_SWEEP_REPO],
        ['GITHUB_REPOSITORY', env.GITHUB_REPOSITORY],
      ];
      for (const [source, raw] of candidates) {
        const value = String(raw ?? '').trim();
        if (!value) continue;
        return { repo: value, source, valid: SWEEP_REPO_SHAPE.test(value) };
      }
      return { repo: DEFAULT_SWEEP_REPO, source: 'default', valid: true };
    }

    ⇒ A named, exported, documented resolver with an explicit precedence chain, imported by name in the very file I grepped. Control: check-half-states.mjs is 20,834 lines, consistent with the self-test the dev cites at :20640.

    ⭐ Why this makes my failure worse rather than more forgivable

    resolveSweepRepo returns its own source. The design already answers the exact question my card claimed could not be answered — "which repo is this answering about, and on what basis?" — and returns it as structured data. ⇒ The capability is not merely present; it is self-describing at the API level. I filed a card asking for a feature that exists, is exported, is documented in two sibling scripts, and is imported by name eight lines from where I stopped reading.

    ⇒ Restating the lesson with the sharper edge: my "control" showed the file parses argv. The thing I needed to notice was in its import list — plain text, in the same file, that no grep for --repo / OWNER / REPO = would ever match. A wrongly-scoped query is not rescued by a control, and a narrow query is not rescued by reading the right file.

    The finding that actually survives — small, real, and now precisely located

    ⛔ Not a capability gap. ⛔ Not the --repo port I proposed. What is left is one line of missing self-description:

    • resolveSweepRepo computes source (PM_SWEEP_REPO / GITHUB_REPOSITORY / default) and hands it back;
    • check-clause2-carriers.mjs's default output prints the repo name without the source — "…derived from 14 open PR(s) in objectstack-ai/objectstack" — so a reader cannot tell a deliberate target from the fallback one;
    • and --help is unhandled: it runs a full network sweep instead of saying any of this.

    ⇒ ⭐ The value is already computed and simply not printed. The honest remedy is to render source in that line — e.g. "…in objectstack-ai/objectstack (source: default; set PM_SWEEP_REPO to target another repo)" — plus a --help that does not hit the network. That is a one-line change to an existing return value, ⛔ not the port this card's body proposes.

    ⚠️ The three "candidate fixes" in this card's body remain built on the false premise and must not be implemented as written. This comment plus 5573481868 are the card's live content; the body is a record of what I ran.

    ⚠️ Still owed by me and ⛔ still not measured: whether dispatch-gates.mjs resolves its repo the same way. If it does, every claim comment my seat wrote today saying it was "NOT recorded as run — it lives only in objectstack" owes a correction of its own.


    Generated by Claude Code

  4. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    ContributorAuthor

    The owed probe is run: dispatch-gates.mjs does NOT share the mechanism, and my claim comments stand

    Closing the loop I left open in 5573481868 and 5573596217. Measured 2026-09-07T16:43Z.

    $ grep -n "resolveSweepRepo\|PM_SWEEP_REPO\|GITHUB_REPOSITORY" scripts/pm/dispatch-gates.mjs
    (no output)
    $ grep -c "objectstack-ai" scripts/pm/dispatch-gates.mjs
    1                                    # control: the file IS greppable
    
    $ PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/dispatch-gates.mjs --tier packages/types/src/views.ts
    dispatch-gates: gate list derived from the tree of 'objectstack-ai/objectstack' at commit 78e8485 …
    EXIT=0
    

    ⇒ The env var does nothing there. ⛔ No correction is owed on this seat's claim comments: their reasoning — dispatch-gates.mjs lives only in objectstack and cannot answer about objectui — holds.

    ⭐ And the reason the two scripts differ is principled, not an oversight, which is the last piece my card got wrong by lumping them together:

    script input ⇒ so the repo is…
    check-clause2-carriers.mjs the GitHub API only — PRs, labels, claim comments (its own header says it "reads no file in the tree at all") a parameter ⇒ an env var can retarget it
    dispatch-gates.mjs the tree on disk — .github/workflows/**, package.json a checkout ⇒ ⛔ no env var can teleport it

    ⚠️ One more thing my card conflated: dispatch-gates.mjs does offer --repo, but as an assertion, not a target — "assert with --repo to make this checkable", i.e. state which repo you believe you are answering about and it refuses if contradicted (objectstack#11126's second half). I read that as a targeting flag and then reported its absence elsewhere as a missing port. ⇒ Two different mechanisms serving two different input models, and I treated them as one feature unevenly applied.

    ⇒ Final shape of this card, replacing everything in its body: one line of self-description is missing from check-clause2-carriers.mjs's default output (render the source that resolveSweepRepo already returns), and --help should not hit the network. ⛔ Nothing else here is a defect.

    ⚠️ Unrelated but observed twice today and worth someone's attention: dispatch-gates.mjs's STALE TREE warning is firing on this container — the checkout is ≥20 commits behind origin/main with 14 of the files it derives from changed. The script says so loudly and correctly (⭐ it is the behaviour this card originally praised); it is a checkout-hygiene matter, not a defect in the script.


    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

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions