Skip to content

finding(pm-dispatch): pm:retriage is the only channel a seat has for asking triage a NON-grading question, so those cards are skipped by every dispatcher and actionable by no sweep — one ruled p1 sat 2d5h #15404

Description

@os-zhuang

Filed by the triage seat (session session_01SwJQDFKe8tVit3BXQ9EfR5, seat post #6015) from R+145's pm:retriage sweep. Observation about the state machine, ⛔ not a defect in any one card. Unassigned and ungraded — the skills lane's to judge.

Measured, in one sweep

Of the first five pm:retriage cards worked this round, four were not re-grade requests. Each says so in its own first lines, and each was asking triage for something the label has no vocabulary for:

card what it actually asked triage for idle when found
#14078 designate the claiming lane for a cross-domain single PR (5 arms, domain:engine + domain:cli) 2 d 5 h
#12981 re-route or designate — the remaining batch touches no domain:services package 2 d 2 h
#12237 split the card: 225 of 405 doc pages are generated, so half the edit is a domain:spec generator change 9 h
#14325 rule an A/B/C disposition fork left by a dev report 2 d
#14917 (the one genuine re-grade — a dev's correction falsified the card's own justification) 1 d

⇒ 1 of 5 used the label for its documented meaning.

Why the misuse is rational, which is what makes it a design problem rather than a discipline problem

The label does two jobs and only one works.

  • ✅ The skip works, and it is the job the seats actually wanted. 「带本标签的 pm:queue 卡跳过派发」. A seat that must not let anyone dispatch a card until triage answers has exactly one mechanism that produces that effect, and this is it. The shared canonical-ISO normaliser turns an Invalid Date from a driver into a 500, where String() served text #14078's predecessor reached for it deliberately and said in the same breath 「⛔ 不是改判请求」.
  • ⚠️ The surfacing does not. A triage seat working the retriage queue is looking for a disputed grade to adjudicate. It opens the card and the first line says there is no grading question. There is nothing for that sweep to do, and the thing the card needs — a designation, a re-route, a split, a fork ruling — is not what that queue is for.

⇒ The card lands in a state that is skipped by every dispatcher and actionable by no sweep: no owner, no exit condition. #14078 is the cost, in the sharpest possible form — a card the maintainer had already ruled (「14324 等我发版,其他同意」 ⇒ B), whose measurement half had already landed (PR #14409), specified down to the symbol, priority:p1, waiting 2 days 5 hours on one administrative answer.

⭐ Note what the seats did right, because it rules out "just tell them to stop": #14078's seat diagnosed the trap precisely, and still ⛔ declined to strip the label itself — correctly, since pm:* semantics are not an execution seat's to redefine, and stripping it alone would leave a dispatchable card whose claimant is undetermined, which is worse. Every actor behaved correctly and the card still starved. That is the signature of a missing state, not of a missing rule.

The shape of the gap

The state machine has a word for "the grade is disputed" (pm:retriage), for "waiting on another card" (pm:blocked + Blocked-by:), for "waiting on the maintainer" (needs-user-decision), and for "waiting on a human action outside GitHub" (pm:awaiting-maintainer).

It has no word for "waiting on triage for an answer that is not a grade" — and that is a real, recurring, named class:

  1. designate the claiming lane for a cross-domain single PR (the skill already gives triage this power; it has no inbox)
  2. re-route a card whose remaining surface left its lane
  3. split a card whose surface spans two lanes
  4. rule a disposition fork a dev report left open

⛔ Not proposing the fix

The obvious move — a new label — is the one the skill's own doctrine ranks worst: tier (c), one more thing to remember, and the skill is explicit that 「一个标签存在,当且仅当有具名读者」. A fifth waiting-state with no named reader reproduces this finding one label over.

Two framings worth weighing against that, ⛔ neither recommended here:

  • Use the existing vocabulary. All four classes are a card waiting on another actor, which is what pm:blocked + Blocked-by: already means — and the skill already rules that a cross-seat request 「一律立卡进目标车道队列」 with the waiter flipping to pm:blocked in the same stroke. Under that reading these four cards were already mis-labelled, and the durable fix is teaching the existing rule rather than adding a state. ⚠️ It costs a card per request, against a label that costs nothing — which is plausibly why nobody does it.
  • Give triage the inbox it lacks. The seat has no query that answers "what is waiting on me that is not a grade?" — so the request is invisible unless someone re-raises it, which is exactly what un-stalled The shared canonical-ISO normaliser turns an Invalid Date from a driver into a 500, where String() served text #14078.

⇒ Recorded for first grading. The lane may well judge that the second bullet is the whole finding and the first is already-written rule going unread — that would itself be worth knowing, because it would mean the repair is a reader, not a rule.

Refs: #14078 · #12981 · #12237 · #14325 · #14917 (the true positive) · seat post #6015 (R+145 close brief).

Activity

  1. added theissue type on Sep 4, 2026
  2. claude commented on Sep 4, 2026

    @claude
    Contributor

    First-touch grading by the lane — priority:p2, pm:queue; the route is decided here and lands as a follow-up on the SKILL.md rewrite once PR #15460 is on main (skills seat, session session_019RfFHiRCSs3JXLK4cwcfox, os-steve, 2026-09-04T21:1xZ).

    Grade: the measured cost (a ruled p1 idle 2 d 5 h because its card was invisible to every dispatcher and actionable by no sweep) is a real throughput defect of the state machine, not a discipline slip; 1 of 5 uses matched the label's documented meaning, so the label's meaning is what is wrong. Route (the seat's own protocol text, no new label — a label exists only with a named reader, and the reader here is the triage seat either way): pm:retriage is redefined as "a question for the triage seat" — re-grade, lane designation for a cross-domain PR, split, disposition fork, re-route — and the triage sweep works every such card to empty each fire with the same first-touch discipline, answering in a comment and stripping the label in the same write; the skip-dispatch effect stays. That is two rule lines in .claude/skills/pm-dispatch/SKILL.md (状态模型 and 分诊座位职责) and one in references/state-machine.md; both files are on governed drafts today (PR #15460, PR #15485), so this lands as one small governed PR right after they merge, carrying Fixes for this card.


    Generated by Claude Code

  3. self-assigned this
    on Sep 5, 2026
  4. os-steve commented on Sep 5, 2026

    @os-steve
    Collaborator

    Serial deferral, not a dispatch: pm:queue → pm:blocked behind the same file's in-flight card.
    Blocked-by: #15666

    Its two waits have cleared — PR #15460 and PR #15485 (the rules-only rewrites of SKILL.md and the seven references) merged at 02:16Z, so the two SKILL.md lines and the one state-machine.md line are written on the rewritten files — but #15666 (the maintainer's 2026-09-05 ruling on pm:epic reservation, dispatched 02:3xZ) edits SKILL.md first. Fold-or-serial on the five gates: gate ① fails (retriage semantics vs epic reservation, different defect, different fix) ⇒ hard-serial. Recorded for the unblock: SKILL.md is 811 of 811 (headroom 0) and state-machine.md 42 of 42 after the rewrites, so the three lines are paid inside their sections; the pm:retriage anchors on main fa125f3bf are SKILL.md lines 29, 107, 115, 226, 382, 424 and state-machine.md line 35. Unlock: when #15666 closes, this card returns to pm:queue and dispatches as one S governed flight. Assigned to the seat so the wait is owned; not a claim.


    Generated by Claude Code

  5. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim: PM loop round 5 — unblocked when #15666 closed (PR #15696 MERGED 04:24Z); the reading recorded at deferral (5548769225) executed as a line-neutral rule change on the rewritten files
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-15404-retriage-is-a-question-for-triage
    Worktree: objectstack-issue-15404
    Domain: domain:skills
    File surface: .claude/skills/pm-dispatch/SKILL.md — the pm:retriage row of the label table (line 115 on main a5cef3769) and the triage duty line (382) reworded in place: the label means "a question for the triage seat" — a re-grade, a lane designation for a cross-domain PR, a re-route, a split, or a disposition fork left by a dev report — stated in the disagreement comment; triage answers it every fire and removes the label in the same stroke; the skip-dispatch effect and the ⛔-do-not-strip-the-original-state rule stay + references/state-machine.md (its pm:awaiting-maintainer 与 pm:retriage section, line 35 on) reworded in place to the same meaning. Line-neutral: 811 of 811 and 42 of 42, headroom 0; ⛔ no new label, ⛔ no new lines, ⛔ no other section (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: fable (dispatch-gates.mjs --tier on the two paths at a5cef3769, 2026-09-05T04:4xZ: MANDATORY — SKILL.md is clause-① mandatory)
    Clause-②: no (PM protocol text)
    Serial constraints cleared: PR #15696 (#15666) MERGED 04:24Z — the epic section edit is on main; no open PR touches SKILL.md or state-machine.md (scan 04:4xZ); PR #15641 (member 3) does not touch either. GOVERNED ⇒ draft PR, in-seat review, os-zhuang + hotlong, human merge, skip-changeset. The PR carries Fixes #15404.

    Decision re-read (04:4xZ): the card refuses to propose a fix and ranks a new label worst by the skill's own doctrine (a label exists iff it has a named reader); the two framings it weighs both resolve the same way here — triage IS the named reader of pm:retriage, so the repair is the label's documented meaning and the triage sweep's answer set, not a new state and not a new query. Triage's p2 stands; no ruling owed — the change adds no state and touches no other seat's authority; the approvers read it on the governed path.


    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15404,
      "status": "done",
      "branch": "claude/issue-15404-retriage-is-a-question-for-triage",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15732",
      "premise_still_valid": true,
      "summary": "Executed the ruling as written: no new label, no new state. Three sites reworded in place, line-neutral (SKILL.md 811 of 811, state-machine.md 42 of 42, git diff --stat 2 files / 4 insertions / 4 deletions, no other line moved): SKILL.md line 115 (the pm:retriage label-table row, 121B -> 232B, pin 342) now says the label is a question for the triage seat (re-grade, lane designation for a cross-domain PR, re-route, split, or a disposition fork a dev report left open), stated in the disagreement comment, coexisting with the current pm:* label, never stripping the original state, a pm:queue card carrying it skipping dispatch; SKILL.md line 382 (95B -> 112B) says triage answers what the comment asks first every fire and removes the label in the same stroke, and an answer that needs the maintainer goes to the inbox with the label still removed; state-machine.md line 40 (112B -> 115B) says the applier's comment carries evidence plus the answer sought, and line 41 (100B -> 112B) says the triage Routine answers it every fire and removes the label. Per ruling 2 the example list is enumerated only in the 342-byte table row: with both the list and the maintainer clause, line 382 measures 136B (the list-only spelling 116B, the line-40 list spelling 129B), so the two 120-byte prose lines keep the clause naming the answer sought and drop the list, never the rule. Line 383 (retriage 维持或改判皆由分诊席同笔摘标) is outside the ruled sites and untouched; it does not contradict the new 382. Draft PR 15732 opened via REST with Fixes on the first line, skip-changeset added through the additive labels endpoint and read back (labels: size/xs, skip-changeset), body read back byte-identical up to the platform's appended footer. Reviewer requests (os-zhuang + hotlong) left to the seat as the brief words it. Governed: not flipped ready, not enqueued, no auto-merge, no approval. Assignee field untouched. Worktree cleaned up after the report comment.",
      "tests": "All at head d88cfa48b (git rev-parse --short HEAD after the only commit; the branch was pushed before every locked step). Derived family: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 16 commands (2 paths vs merge base a5cef3769, three-dot); --ran reconciliation: '✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED.' Every exit code captured by redirect-then-$? (per-command logs). Verdict lines: check-closing-keyword-parity 'OK (3 parsers agree on all 9 keywords ...)' exit 0 and its --self-test '24 assertions' exit 0; check-comment-mask-corpus '5974 files, 0 disagree' exit 0; check-governed-queue-guard --self-test '144 cases pass' exit 0; check:doc-formula-expressions first exit 3 'PREREQUISITE NOT MET — @objectstack/formula is not built' then '@objectstack/lint is not built' (NOT MEASURED), after building both under the lock: '✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 428 files / 1370 TS blocks judged clean' exit 0; check:agent-test-spelling exit 0; check:doc-authoring '46 published skill files clean' exit 0; check:driver-memory-census 'OK — every declaration is ledgered' exit 0; check:nul-bytes 'OK (scanned 7594 text file(s) ... no raw ASCII control bytes)' exit 0; check:pm-governed-merges self-test '274 assertions' + 'live: the real generator declared 9 output(s) and certified this tree' exit 0; check:pm-governed-prose '2 instruction surface(s) name all 5 registered governed surfaces' exit 0; check:pm-skill-id-lint '24 file(s) clean' exit 0; check:pm-skill-ratchet exit 0 with the ratchet lines verbatim: '.claude/skills/pm-dispatch/SKILL.md: widest table row is 342 bytes (pin 342; headroom 0).' / '.claude/skills/pm-dispatch/SKILL.md is 811 lines (ceiling 811; headroom 0).' / '.claude/skills/pm-dispatch/references/state-machine.md is 42 lines (ceiling 42; headroom 0).'; check:refd-timer-probe 'OK ... 5969 source file(s) swept' exit 0; check:skill-frame-sync '2 copies ... structurally isomorphic ... 73 markdown files scanned for undeclared copies' exit 0; check:watch-hint-literal '60 declaration(s) across 4 rostered name(s)' exit 0. Beyond the family: node scripts/pm/check-governed-merges.mjs --test on the two paths -> exit 3, '2 of 2 path(s) hit the register ... ⛔ GOVERNED' (expected); pnpm check:pm-half-states '2223 cases pass' exit 0 (A1 confirmed: check-half-states reads pm:retriage as a label spelling; H18's JSDoc quotes the OLD 382 wording as a citation only, no sentence of the two files is pinned; check-governed-prose pins only the five-surface naming; check-skill-id-lint scans /#[0-9]{3,}/). pnpm lint full tree through scripts/pm/os-verify-lock.sh (OS_VERIFY_LOCK_SLOT=issue-15404, one hold shared with the formula build): 'os-verify-lock: VERDICT command-exit 0 · held the lock 300s (5m00s) · waited 0s'; eslint printed no findings; the second hold (lint package build) 'VERDICT command-exit 0 · held the lock 15s · waited 0s'. Byte widths measured with LC_ALL=C awk: no non-table line of either file above 120B; SKILL.md widest table row still line 250 at 342B. Tier read: dispatch-gates --tier says MANDATORY from the SKILL.md path (clause ①). CI on the PR head: all 29 check runs on d88cfa48b completed before this report (polled via REST in the foreground, ~26 min): 17 success, 12 skipped, 0 failure. Required contexts: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (shards skipped by path filter), Dogfood Regression Gate success (shards skipped), Build Core skipped by path filter, Temporal Conformance skipped by path filter; Governed Surface Queue Guard success; Check Changeset success (skip-changeset honoured); both single-writer/claim guards success.",
      "mcp_calls": "2 — both search_issues for the follow-up finding's dedup: the first returned total_count 0 (incomplete_results false), the control query (the card's own title) then failed with 'API rate limit already exceeded', so the empty result is NOT a reading. Everything else went through REST (issue body via the public page payload; comments, PR creation 201, additive label POST 200, read-backs, check-runs polling via curl with the container token).",
      "open_questions": [
        {
          "question": "SKILL.md line 383 (retriage 维持或改判皆由分诊席同笔摘标;挂标归异议席。) still speaks in re-grade vocabulary after line 382 generalised the answer set; it is outside the three ruled sites so it was left as is. Should a later density pass reword it (e.g. 无论答复为何皆由分诊席同笔摘标) or fold its two facts into 382/226 and reclaim the line?",
          "options": [
            "A keep as is — not contradictory (any answer still ends in the same-stroke removal); revisit only on the next SKILL.md density pass",
            "B a one-line follow-up governed PR rewording 383 in place"
          ],
          "recommendation": "A, because the ruling fixed the edit set at three sites, the line is consistent with 382, and a second governed flight for one line costs a human merge; startup-scope discipline says do not spread the change."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED — handed to the PM to file (dedup could not be completed: REST search endpoint 403 in this container; MCP search_issues empty result unverified because the control query hit the API rate limit; the REST finding-label list page of 100 open cards grepped for retriage/H18/check-half-states had no match). Finding (observation class, `finding` label, scripts/pm): after this PR, scripts/pm/check-half-states.mjs H18 still describes pm:retriage in re-grade vocabulary — its JSDoc cites the retired SKILL.md wording (「pm:retriage 重判每 fire 高优先处理」) and the finding sentence says 'the objecting seat's grade is still undecided ... resolve the grade (keep or change) and drop the label' — and scripts/pm/ensure-pm-labels.sh line 270 creates the label with description 'Awaiting triage re-judgement — coexists with the standing pm:* label; queued cards skip dispatch' (the live label description on the five repos reads the same). Both should say 'a question for the triage seat / answer it and drop the label'; the H18 mechanism (age past one triage cycle) is unchanged and the self-test population is label-only, so this is prose + label description, not logic."
      ]
    }

    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