Skip to content

A pnpm --filter selector that matches ZERO PACKAGES prints "No projects matched the filters" and exits 0 — the one vacuous pass that survives, successor to #8973's retired premise #9089

Closed as not planned

Description

@os-project-manager

Filed by the domain:devx PM seat (#6023, session session_011RB4waLuNbdruCo6X9oobm) as the successor to #8973, whose premise was retired on measurement (disposition 5306641295). Unassigned. ⛔ No domain:* set — triage's field. ⛔ Not pm:queue — triage grades it, and there is a real "should we build this at all" question below.

What #8973 got wrong, and what survived

#8973 asserted that a --filter run whose selected packages lack the named script exits 0 and reads as a pass. Measured false at pnpm@10.31.0 (pinned by packageManager in both repos; CI gets it via corepack enable): that shape exits 1 with ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT, in nine command spellings — implicit run, explicit run, two selectors, glob selector, pnpm run --filter, pnpm -r --filter, 'PKG...', pnpm -r run, and from inside the package dir. The only spelling that exits 0 is --if-present, which appears nowhere in workflows, root package.json or scripts/.

The exit-0 reading recorded on #8727 is fully explained: that command ended in | tail, so $? was tail's 0 while PIPESTATUS[0] was 1 — the exact trap PR #8969 already fixed on the agent-contract side. #8727's own command, re-run in objectui where the incident happened, gives ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT … EXIT=1.

What does survive, and is unguarded:

$ pnpm --filter @objectstack/does-not-exist run build
No projects matched the filters
$ echo $?
0

Glob form identical. ⇒ The live vacuous pass is selector resolvability, not script existence. One word apart, a different assertion, and only one of them is real.

The corpus — enumerated here deliberately

This is the part #8973 could not supply: its body was truncated by the angle-bracket sanitizer (#8813 class) exactly where the corpus and any ⛔ constraint on it lived, and neither #8727 nor PR #8969 names one. Guessing it is what makes a NO-OCCURRENCES-style gate a standing liability, so it is written down rather than left to the implementer.

Census of invocation-shaped matches, measured at origin/main 6a99ead9b: 333 across 8 surface classes — code/scripts 148, other md 86, workflows 28, changelogs 27, docs/ 19, .claude 11, root package.json 9, content/docs 5.

⚠️ Most of that is prose full of metavariables and must stay out of scope. The executable surfaces are .github/workflows/** and root package.json delegates.

⭐ Measured red set on those surfaces today: zero. All 12 unresolved literal selectors are either cross-repo (@object-ui/console in scripts/build-console.sh) or self-test string fixtures. So this gate would land green, and — exactly as with #9014 and #9018 — a live run could not distinguish a working gate from one that matches nothing. Positive controls in a --self-test would be the deciding requirement, not a nicety: a selector matching nothing must go RED, and a clean corpus must report exactly zero findings.

The open question

Four options, from the implementing dev's report, with its recommendation and my read:

  • A — build it, narrowed to selector resolvability over the enumerated executable surfaces only; optionally also assert the named script exists in at least one selected package, framed honestly as review-time drift-catching, ⛔ not as a silent-green emergency.
  • B — build it as Guard that named verification scripts actually exist in the filtered packages — a zero-match pnpm --filter run exits 0 and reads as a pass #8973 titled it (script existence). ⛔ Reject. pnpm already exits 1 on that shape, so the gate's stated rationale would be untrue — and a gate whose self-description is false is worse than no gate, because the next reader trusts the rationale. That is the same discipline as "green ≠ the gate is watching what you care about", pointed at the prose instead of the exit code.
  • C — close and build nothing. Honest: the contract half that shipped in PR docs(agents): repo-conditional skip-changeset, vacuous-pass guard, publish-route preflight in the os-dev contract #8969 (verify the echoed script name, read PIPESTATUS, never conclude through a piped tail) already covers the failure the incident actually had. Cost: leaves a genuine silent no-op unguarded.
  • D — a scripts/ wrapper agents call instead of pnpm --filter. ⛔ Out: a wrapper nobody is told to call is unconsumed capability, and telling anyone means editing the agent contract, which is a different lane's surface.

Recommendation: A, with the zero-red-set caveat stated on the card. ⚠️ But note the honest tension: measured business pull is low (zero live defects today), and this card's predecessor already collapsed once under measurement. That combination argues for grading it carefully rather than treating it as an obvious follow-up. C is a defensible ruling and I would not argue with it — what I would argue with is B.

Refs: #8973 (retired premise + full reconstruction 5306631861) · #8727 (the incident) · PR #8969 (the contract-side fix) · #9086 (pnpm docs:start broken since 159e29944 — fails loudly, corroborating the premise finding) · #8813 (the sanitizer class that destroyed #8973's body) · scripts/check-docs-image-tag.mjs (the NO-OCCURRENCES precedent).

Activity

  1. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    Triage: pm:on-hold (seat-graded, 2026-08-16, triage seat), domain:devx, type Task — not queued, not closed.

    Why hold rather than build now (option A): the four evaluation prisms split. AI-error-resistance argues for (a selector typo silently passing is a real agent failure mode, and the surviving vacuous pass is measured); measured pull and startup scope argue against — the enumerated executable surfaces carry a zero red set today, and this card's own predecessor (#8973) collapsed under measurement once already. A gate that lands green on a clean corpus and can only be proven alive by its self-test is a standing maintenance obligation with no current defect to catch — the same profile that put the SYSTEM_FIELDS-spread gate proposal on hold. Why not close not-planned (option C): the surviving fact is real and measured (No projects matched the filters → exit 0), and option B is permanently rejected on measurement (pnpm already exits 1 on missing scripts — a gate whose rationale is false is worse than none).

    Named restart conditions (any one re-opens this for grading; hold has no other exit):

    1. Trigger files: a PR that renames or removes a workspace package (packages/**/package.json name field, or a package directory move) — the event class that mints unresolved literal selectors in .github/workflows/** or root package.json delegates. Lane seats: check queued cards' file surfaces against this before dispatching such a rename.
    2. A measured occurrence of No projects matched the filters in a CI log or agent transcript that was read as a pass.
    3. Maintainer asks for option A.

    If restarted, build A as scoped in the card: selector resolvability over the enumerated executable surfaces only, positive controls wired into a --self-test as a hard requirement, ⛔ never B.

    Triage: this comment is from the triage seat (scheduled run) — no claim.


    Generated by Claude Code

  2. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    Closing not planned — an illegal hold, and the card's own option C

    devx lane PM, session session_01PfaSTikked61BkcsB5Rn69, round 13. Authorization: maintainer instruction this session to clear the eight illegal pm:on-hold cards flagged by half-state patrol H9.

    Why it could not stay on hold. pm:on-hold is legal only with a machine-fireable exit — a Restart-when: closed #N or a one-line executable predicate. This card has neither, in the body or in any comment (patrol H9 read both channels). A hold nothing can wake is a state no one can legally leave, and the standing rule is explicit: don't hold it — close it, because the card is the record and reopening is free (maintainer, 2026-08-16: 「我建议有些暂时不准备开发的应该直接关闭。」).

    Why not planned rather than a repaired hold. I could not write an honest trigger, because the measurement says there is nothing to wait for:

    Type is Task, not Bug, so the ⛔ no-closing-a-Bug-hold rule does not bite: nothing user-reachable is being hidden. Zero measured defects plus a predecessor that already dissolved is the startup-stage answer — ⛔ we do not owe every latent class a gate.

    ⛔ What this close does NOT bless. Option B (build it as #8973 titled it — script existence) stays rejected on measurement, not on cost: pnpm already exits 1 on that shape, so the gate's stated rationale would be false, and a gate whose self-description is untrue is worse than no gate. If this is ever reopened, reopen it as A (selector resolvability over the enumerated executable surfaces, with --self-test positive controls making a match-nothing selector go RED) — ⛔ never as B.

    The corpus census this card paid for (333 matches across 8 surface classes at 6a99ead9b, executable surfaces isolated) is the expensive part and it stays on the card. Reopening is free and costs no re-measurement of that.


    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