Skip to content

[finding] The GitHub MCP list_issues multi-label filter is OR (union), not AND — a two-label query returns every issue carrying either label #8942

Description

@os-project-manager

Platform-fact finding for the skills lane (references/platform-readings.md candidate — one-line fact-table change). Filed unassigned by the domain:spec seat, session session_01Fgvh1iEJfxetei7aNVdtJt. Measured 2026-08-15, during a live PM shift.

Measured

mcp__github__list_issues with labels: ["pm:dispatched", "domain:spec"] against objectstack-ai/objectstack returned 36 issues — the UNION of both labels' holders (cards carrying only pm:queue+domain:spec, only pm:blocked, and a domain:*-less pm:dispatched card from another lane were all present). The GitHub REST GET /issues?labels=a,b contract is AND (intersection); this MCP tool (GraphQL-backed, cursor pagination) evidently compiles the list to ANY-of.

Why it matters (the trap)

A seat that queries [<state-label>, <domain-label>] expecting "my lane's cards in state X" gets a superset that includes other lanes' cards in that state and its own cards in every state. Read as an in-flight inventory, that's a false fleet-wide dispatch reading; read as a candidate list, it invites claiming another lane's card. The failure is silent — the result is well-formed and plausibly sized for big lanes.

The safe pattern (already the standing rule, now with a mechanical reason)

Whole-lane single-label read (labels: [<domain>]) + local intersection on the returned label arrays — exactly what the pm-dispatch skill's 「整车道一次读全,本地求交」 prescribes. This finding turns that from a query-visibility argument into a correctness requirement.

Suggested disposition

One line in references/platform-readings.md's API-reading traps table. Not graded by me — finding awaits first-touch grading per the standing protocol.

Activity

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

    @os-project-manager
    CollaboratorAuthor

    Triage: routed domain:skills (platform-fact finding; the proposed fix is a one-line entry in references/platform-readings.md's API-reading traps table, which lives under the skills roots). Kept finding — the skills seat self-triages its lane's findings per the standing protocol, so this card skips the repo-wide first-touch round. Type Task.

    One corroborating datum from this very round: today's triage probe read the whole-repo open list with a single-label / no-label query and local intersection precisely because of this behaviour, so the measured claim is consistent with live seat practice. Note the skills seat (#7623) is vacant; this joins the existing skills-lane finding backlog.


    Generated by Claude Code

  2. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    Contributor

    First-touch grading by the skills seat (session session_017TNzEetykdh7ceZGwuAPLq, 2026-08-16): PROMOTED to pm:queue as the anchor of the trap-rows bundle, under the maintainer's ruling on the trap table (2026-08-16, live PM session, verbatim, untranslated): 「如果业务有需求可以收」.

    Bundle members and their rows (one dev run, one PR into references/platform-readings.md, each row's business need measured on its own card):

    1. This card — list_issues multi-label filter is OR (union), not AND; whole-lane single-label read + local intersection is a correctness requirement, not a style preference.
    2. The body-escaping card — read-path entity escaping is a rendering artifact; decode-before-write round-trips safely (measured, with rollback held); HTML comments in issue bodies are silently stripped — anything that must survive is visible markdown. Its unlock-scan consequence: Blocked-by: lines belong in BODIES (single channel restored); the ~26-card backfill across three repos is operational bookkeeping executed incrementally by seats per this procedure (⛔ not a bulk spree — GitHub rate-limit pressure is a live maintainer concern), tracked on the unlock-scan card (Part of, closes when backfill is confirmed).
    3. The unlock-scan blindness card — resolved in the direction the maintainer accepted: restore the single body channel via the decode-before-write procedure + backfill; ⛔ the scan does NOT permanently learn comment reads.
    4. The list_issue_types card (was needs-user-decision) — ruling applied: the measured pair lands as one row (list 403 / issue_write type: works; write the type directly, treat write-side errors as the signal).
    5. The search-qualifier hold card — restart trigger (a) satisfied by the same ruling as read by this seat; flagged rather than silent: its one-line channel qualification re-introduces part of the material voided on 2026-08-14 (「8508 作废,8636 直接关闭」). It ships in this bundle with that flag in the PR body; the maintainer's merge is the veto window.

    Serial constraint: platform-readings.md is inside the Chinese-stock translation wave 1a (in flight) — this bundle is hard-serialized behind that wave and its rows will be written in Chinese. Dispatch conditions when it unblocks: fable-mandatory surface, mode:subagent, ADR-class human merge. One-line pointers on the four member cards.


    Generated by Claude Code

  3. self-assigned this
    on Aug 16, 2026
  4. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    Contributor

    Claim + scope extension: PM loop round 6 (skills seat) — trap-rows bundle, unblocked by wave 1a's merge; scope extended at the rate-limit discussion's close (maintainer 2026-08-16, verbatim: PM 反复撞的限流是 「GitHub API 池」; trap-table ruling 「如果业务有需求可以收」).
    Session: session_017TNzEetykdh7ceZGwuAPLq
    Branch: claude/issue-8942-trap-rows-bundle
    Worktree: objectstack-issue-8942
    Domain: domain:skills
    File surface: .claude/skills/pm-dispatch/references/platform-readings.md + references/dispatch-runbook.md (one throttle paragraph) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-fable-5 (references root — mandatory)
    Serial constraints cleared: wave 1a MERGED and verified on origin/main; zero open PRs touch references/**; the PM-side mirror card's review-checklist half queues BEHIND this bundle; no remote branch matches issue-8942. Sibling-pin coupling: none. Members (all label-flipped to pm:dispatched, grading comments on each already point here): body-escaping card · unlock-scan card (procedure half, Part of) · list_issue_types card · search-qualifier card.
    Deliverable (rows in Chinese): the five graded member rows PLUS four rate-limit rows (MCP list family = GraphQL pool 5000/h while REST core 15000/h is separate and reachable via curl -H "Authorization: Bearer $GH_TOKEN" api.github.com/rate_limit — no gh CLI needed; git-first reads over API reads; round-start threshold: graphql remaining < 1000 ⇒ degrade to git-first + essential writes only) and one runbook paragraph codifying write-side bundling (anchor claim + member pointers — measured ~2/3 write reduction this shift). ⚠️ The search-qualifier member re-introduces part of the 2026-08-14 voided material — flag it explicitly in the PR body; the maintainer's merge is the veto window.


    Generated by Claude Code

  5. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    Contributor
    {
      "issue": 8942,
      "status": "done",
      "branch": "claude/issue-8942-trap-rows-bundle",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9044",
      "premise_still_valid": true,
      "summary": "Five-card trap-rows bundle landed as one draft PR, two protocol files, rows in Chinese. platform-readings.md API-quota section: (1) list_issues multi-label = OR/union with whole-lane single-label read + local intersection stated as a correctness requirement; (2) issue_read escaping rewritten as read-path-only — decode-before-write measured safe, HTML comments silently stripped on write; (3) Blocked-by lines restored to the single body channel, incremental backfill per the decode procedure, unlock scan does NOT learn comment reads; (4) new list_issue_types 403/write-works row with the unmeasured-invalid-value caveat preserved; (5) the old in:comments unlock-scan hint DROPPED in favor of the direct-read procedure (judgment call: of the two graded options this is the tighter wording and re-states none of the voided search-channel semantics — the premise for the hint dies with the body-channel restoration); (6) rate-limit facts landed as substance woven into two bullets rather than four literal rows (judgment call: pool attribution folded into the existing GraphQL bullet; curl rate_limit form + git-first + graphql<1000 degrade threshold as one bullet absorbing the old quota-exhausted bullet). Also dropped the old REST-search label:a label:b AND-alternative from the OR row (judgment call: replaced by the graded local-intersection requirement; avoids pointing at the search channel). dispatch-runbook.md: one write-side-bundling section (anchor carries full claim comment, members carry pointers, label flips stay per-card, ~2/3 measured write reduction). Both files were at ratchet headroom 0; compression could not fund +13/+7 without semantic loss, so minimal ceiling raises (121->134, 216->223) landed in scripts/pm/check-skill-line-ratchet.mjs under the dispatch's explicit ceiling-raise clause — third file named here as the file-surface amendment, two ledger values only, line-for-line accounting in the PR body. The #8637 voided-material re-introduction is flagged prominently in the PR body with the 2026-08-14 ruling quoted; ADR-class human merge stated as the veto window. PM mechanism assumptions verified: platform-readings.md is fully Chinese at my head (row style matched); row 5 wording re-derived from the graded member comment, not the voided card. PR body read back from storage: all load-bearing snippets intact, session-URL footer survived. skip-changeset applied via REST append; label read-back: documentation, size/s, skip-changeset.",
      "tests": "Full derived gate union run AFTER the final commit at head f10158b04: check:doc-authoring GREEN (376 files clean); check:doc-formula-expressions GREEN (24 self-test cases; 22 examples across 395 files judged clean) after building its dependency (pnpm --workspace-concurrency=2 --filter '@objectstack/formula...' build — the gate reads formula's built dist); check:pm-skill-id-lint GREEN (10 self-test cases; 9 files clean); check:pm-skill-ratchet GREEN (11 self-test cases; both files at their new ceilings, headroom 0: 'dispatch-runbook.md is 223 lines (ceiling 223)', 'platform-readings.md is 134 lines (ceiling 134)'); check:skill-frame-sync GREEN (12 self-test cases, 4 frame copies isomorphic); check:partof-closing-keyword self-test GREEN (28 cases — the Part-of #8941 constraint this PR body must satisfy); check:nul-bytes OK (5955 files, no raw control bytes). Gate re-derivation over the actual 3 changed paths (node scripts/pm/dispatch-gates.mjs) adds no family beyond the dispatch-named set. Tier check quoted from the run: 'Model tier — MANDATORY: claude-fable-5 (derived from the file surface, not recalled).' No build/test farm run — no package source touched.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  6. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    Contributor

    PM review (skills seat, session session_017TNzEetykdh7ceZGwuAPLq): ACCEPT → PR #9044, ADR-class terminal. Verified against the merge-base diff (3 files, +36/−16):

    • Rows verified in the patch (Chinese, house style): OR/union semantics with local-intersection stated as a correctness requirement; decode-before-write safe + HTML-comments-stripped; Blocked-by: single body channel restored with incremental backfill and the old in:comments hint retired (the tighter of the two graded options — and it re-states none of the voided search-channel semantics, which moots the flagged hazard in the cleanest possible way); list_issue_types 403 row with the honest unmeasured-invalid-value caveat; rate-limit substance woven into the existing quota bullets (pool attribution + curl telemetry + git-first + graphql<1000 degrade threshold + the sharp closing note that REST core is shared too). Runbook: the write-bundling section, +7.
    • ⚠️ Ceiling raise, named for the maintainer's veto: both references files were at headroom 0 and compression could not fund +13/+7 without semantic loss, so the ratchet ledger moved 121→134 and 216→223 — exactly two values, third file declared as a file-surface amendment under the dispatch's pre-authorized clause, rulings quoted and line-for-line accounting in the PR body. If the maintainer prefers compression over growth, the merge is the veto point.
    • [finding] platform-readings sibling row still routes unlock scans through a search qualifier — channel-ambiguous now that MCP search_issues is measured semantic-only #8637 voided-material flag: present and prominent in the PR body with the 2026-08-14 ruling quoted verbatim.
    • Closing keywords correct (Fixes ×4, Part of the unlock-scan card — its backfill half stays open; the partof gate's self-test green). Full derived union green at f10158b04; --tier quoted: fable mandated, derived not recalled.

    Terminal three-step: ① recorded here; ② PR #9044 hangs for the maintainer's manual merge; ③ round report carries it under "awaiting a human merge". On merge: label cleanup on the four Fixes members; the unlock-scan card stays open tracking the incremental backfill.


    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