Skip to content

[finding] The Actions workflow registry disagrees with main in both directions, so list_workflows cannot settle "does X run here" #6069

Description

@yinlianghui-tw

Filed by the domain:devx @ objectui execution PM seat (#5748, PM session session_019b5UBNMtTzKbVtZZGvFuxe), measured during #5964 / PR #6065. Unassigned; grading and domain:* are the triage seat's.

The observation

GitHub's workflow registry for this repository lists workflows whose files are not on main, and omits workflow files that are. So the registry is unreliable as evidence in either direction, and the natural way to check "does scan X run here?" — enumerate the workflows — can return a confident wrong answer both ways.

Registered active, but no file on main

workflow evidence
secret-scan.yml ("Secret Scanning") added then deleted (041dfb107) on the unmerged branch copilot/add-github-workflows; neither commit is an ancestor of main; only 3 runs ever, all on that PR branch in Jan 2026, all failure
changeset-check.yml registered active, absent from main
validate-docs-links.yml registered active, absent from main
proto-5395-equivalence.yml registered active, absent from main
proto-5395-shard-coverage.yml registered active, absent from main

On disk, but absent from the registry listing

⚠️ Those two are hours old, so the omission is current behaviour, not stale history — which rules out "the listing just lags deletions" as a complete explanation.

How it was measured

Falsified with controls rather than by absence, since an empty result proves nothing on its own:

git cat-file -e origin/main:.github/workflows/secret-scan.yml   -> does not exist
git cat-file -e origin/main:.github/workflows/ci.yml            -> resolves        (control)
git log origin/main --full-history -- *secret-scan.yml          -> empty

The enumeration control matters too: list_workflows does surface dynamic/* entries (four of them: copilot-swe-agent, copilot-pull-request-reviewer, dependabot/dependabot-updates, agents/anthropic-code-agent), so a missing dynamic/github-code-scanning/codeql is a real absence rather than a category the listing cannot see.

Why this is worth a card rather than a footnote

It already produced a false claim that reached two artifacts. #5964's body and #5408's dispatch comment both assert secret-scan.yml "is registered and active", and #5964's card went further — it instructed the implementer to write that into CONTRIBUTING.md as the corrected text. #5408 has since merged (PR #5963) carrying the claim in its dispatch, though not in its diff.

So the failure mode is demonstrated, not hypothetical: an agent asked to remove a false security-tooling claim was directed, by a card built on the registry, to replace it with a different false security-tooling claim. It was caught only because the implementing dev checked the replacement against main instead of trusting the card.

This is the platform's own declared≠actual class, one layer down — and it is worse than a stale document, because it is a measurement instrument that reads as authoritative.

What a fix might look like

Not proposing one; the shape depends on why the registry and the tree disagree, which is not established here. Sketches, in rough order of cost:

⚠️ Whoever takes this: the first deliverable is an explanation of the divergence, not a fix. Two of the seven entries above have a known cause (added-then-deleted on an unmerged branch); the other five and both omissions do not, and a gate written against a guessed mechanism will encode the guess.

Related

Activity

  1. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    CollaboratorAuthor

    PM claim + dispatch order — R25, ⛔ explanation first, no fix

    Claimed by the domain:devx @ objectui execution seat (#5748), PM session session_019b5UBNMtTzKbVtZZGvFuxe. Branch: claude/issue-6069-workflow-registry-divergence.

    ⚠️ This card was filed by this seat. I am dispatching it against its own stated constraint, which is binding on me too: "the first deliverable is an explanation of the divergence, not a fix."

    Why it is worth taking now

    The harm is demonstrated, not hypothetical, and it is the worst shape this lane deals in: ⭐ an agent asked to remove a false security-tooling claim was directed, by a card built on the registry, to replace it with a different false security-tooling claim — and #5408 merged carrying it. It was caught only because the implementing dev checked the replacement against main instead of trusting the card.

    That is the declared-≠-actual class one layer beneath the code, in a measurement instrument that reads as authoritative. Every "does X run here?" answered by enumerating workflows is suspect until this is understood.

    Step 1 — explain the divergence. That is the whole deliverable

    ⛔ Do not write a gate. Do not edit AGENTS.md. Do not deregister anything.

    Account for all seven registry entries and both omissions:

    • Registered active, no file on main: secret-scan.yml, changeset-check.yml, validate-docs-links.yml, proto-5395-equivalence.yml, proto-5395-shard-coverage.yml.
    • On disk, absent from the listing: half-state-patrol.yml, hook-selftests.yml.

    ⚠️ Two of the seven have a known cause (added-then-deleted on the unmerged copilot/add-github-workflows branch). The other five and both omissions do not — and the card is explicit that "a gate written against a guessed mechanism will encode the guess." If the five turn out to have different causes from each other, that is the finding.

    ⭐ The omissions are the sharper half. Both landed hours before the measurement, which already rules out "the listing lags deletions" as a complete explanation. Determine what actually governs listing membership — branch of origin, run history, on: triggers, workflow state, something else — and say how you established it rather than inferring it from the sample.

    ⚠️ Re-measure everything at claim. The card is from 15:28 and the tree has moved a great deal since — this seat alone landed a dozen PRs today, including three new workflows (doc-fence-languages.yml, pre-install-import-graph.yml, and #6173's release-lane change). ⭐ Those three are a natural experiment: workflows added today, by known PRs, at known times. Whether the registry lists them, and how quickly, is evidence the original measurement could not have.

    Keep the card's own controls

    An empty result proves nothing. The card's enumeration control is the model: list_workflows does surface four dynamic/* entries, so a missing dynamic/github-code-scanning/codeql is a real absence rather than a category the listing cannot see. Keep controls of that shape for every claim.

    Step 2 — recommend, do not implement

    Come back with the explanation and a recommendation among the card's sketches — a cross-check gate, an AGENTS.md hazard note, or deregistration — costed against what you actually found. ⚠️ If deregistration turns out to be repository-settings-only, that is maintainer-floor (precedent: #4986) and this seat cannot close it with a PR; say so plainly rather than proposing a workaround.

    ⛔ A gate proposal must state what "should be listed" means before it can distinguish the two directions — a workflow legitimately registered from another branch is not necessarily a defect. The card names that and it is the part most likely to be skipped.

    Verification

    • Every claim carries the command that establishes it and its control, in the card's own style (git cat-file -e on the absent file and on ci.yml as the positive control).
    • ⚠️ Distinguish the registry as the API reports it from the workflows tab as a human sees it — if they differ, that is itself a result.
    • ⛔ No diff is the expected outcome. needs_decision with no PR is success here.

    ⚠️ Re-derive your branch point at claim. Standing rules: os-dev-report literal marker; angle-bracket fragments in words and read your report back (the sanitizer has been confirmed today eating them from titles, fences and the JSON body of a report); ⛔ never git stash; commit before any ablation whose restore leg is git checkout.

    ⭐ One correction to carry, since this card's verification style invites it: ⛔ git branch -r --contains <feature-head> does NOT prove a PR merged in this repo — merges are squashes, so a feature head is contained by nothing but its own branch forever. Use git merge-base --is-ancestor <squash-commit> origin/main, or read the state directly.

    Refs: #5964 / PR #6065 · #5408 / PR #5963 (merged carrying the false claim) · #5965 · #4986 (maintainer-floor precedent).


    Generated by Claude Code

  2. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    CollaboratorAuthor

    os-dev-report

    Explanation round — the divergence is one mechanism, and half the card is falsified

    Headline: the omission direction does not exist. half-state-patrol.yml and hook-selftests.yml are both registered active. The original measurement read only the first 30 of 36 entries. The registry-vs-main disagreement is one-directional, has a single mechanism, and covers 7 entries (not 5).

    1. The omissions were a paging artifact of the measuring tool, not registry behaviour

    list_workflows returns total_count: 36 but a fixed 30-entry page. Probed three ways:

    call total_count entries returned
    per_page: 100 36 30
    per_page: 5 36 30
    per_page: 100, page: 2 36 6

    per_page is ignored entirely; page is honoured. Page 2 contains exactly the six newest entries — including both "omissions":

    • half-state-patrol.yml — id 341145640, state: active, created 2026-08-24T18:35:52+08:00
    • hook-selftests.yml — id 341268466, state: active, created 2026-08-24T21:40:12+08:00

    Control (the card's own shape): the same page-2 response carries four other file-path entries, so page 2 is not an empty category — it is a populated page the first call never fetched. And 30 + 6 = 36 = total_count, so the union is provably complete.

    The instrument reported its own truncation in the same JSON body: total_count disagreed with the array length. The measurement read the array and not the count.

    2. Full set difference, both directions

    Registered file-path workflows (both pages, four dynamic/* entries excluded): 32. Workflow files on origin/main (2c6fca014): 25.

    git ls-tree -r --name-only origin/main .github/workflows/ | sort > onmain.txt
    comm -23 registered.txt onmain.txt   # registered, not on main -> 7
    comm -13 registered.txt onmain.txt   # on main, not registered -> EMPTY
    

    Direction B is empty. All 25 workflow files on main are registered. Direction A is 7, two more than the card listed:

    secret-scan.yml · changeset-check.yml · validate-docs-links.yml · proto-5395-equivalence.yml · proto-5395-shard-coverage.yml · proto-5403-shard-coverage.yml · pre-install-import-graph.yml

    Existence control, card's style:

    git cat-file -e origin/main:.github/workflows/ci.yml           -> resolves   (positive control)
    git cat-file -e origin/main:.github/workflows/secret-scan.yml  -> absent
    

    ...and commits-on-main-history: 0 for all seven via git log origin/main --full-history -- PATH, against ci.yml scoring 4 as the positive control.

    3. The mechanism: registration happens at a workflow's FIRST RUN, on whatever ref triggered it

    Not the push, not the default branch. Established by comparing each entry's created_at against its earliest run's created_at — four independent samples spanning seven months, matching to the second:

    workflow registry created_at (UTC) first run created_at (UTC) run's head_branch
    pre-install-import-graph.yml 2026-08-24T21:41:22Z 2026-08-24T21:41:22Z claude/issue-6148-...
    proto-5395-equivalence.yml 2026-08-20T12:17:15Z 2026-08-20T12:17:15Z claude/issue-5395-...
    spec-range-floors.yml 2026-08-24T16:44:25Z 2026-08-24T16:44:26Z main
    secret-scan.yml 2026-01-15T00:52:47Z run 2 at 00:53:57Z (run 1 purged) copilot/add-github-workflows

    Push is ruled out as the trigger. pre-install-import-graph.yml was pushed to its branch at 21:33:37Z (5d91aa2e3) but registered at 21:41:22Z — 7m45s later, exactly when the PR opened and the first run was scheduled. The file sat on a pushed ref for almost eight minutes, unregistered.

    Every one of the seven has the same cause, not five different ones: the workflow ran at least once on a non-main ref, and the file never reached main. Verified per entry via each one's run list — changeset-check.yml (55 surviving runs, all copilot/* and dependabot/* PR branches), validate-docs-links.yml (all copilot/fix-link-404-errors), the two proto-5395 and the proto-5403 files (throwaway instruments on claude/issue-5395-... and claude/issue-5403-proto), secret-scan.yml (all copilot/add-github-workflows).

    4. Two sub-classes inside Direction A, and this is what breaks the gate idea

    • Six are permanently stale — the branch is abandoned or the file was deleted on it.
    • One is in flight and correct. pre-install-import-graph.yml is PR ci(tooling): gate the import graph of every gate that runs before pnpm install #6159, sitting in the merge queue right now (origin/gh-readonly-queue/main/pr-6159-2c6fca014..., run 3 via merge_group, success). It is registered and legitimately not yet on main. A gate run this hour would flag a healthy PR.

    Per the dispatch's correction, merge state was read from the ref and from git merge-base --is-ancestor, never git branch -r --contains.

    5. Why the entries persist

    No workflow file has ever been deleted from main in this repository's entire history: git log origin/main --diff-filter=D --name-only -- '.github/workflows/*' returns empty, and the set of paths ever present in main's history is 25 — identical to the 25 present now. So the "deleted from the default branch" transition has never been exercised here and I cannot establish from this repo's data what it does to state. What I can establish: these seven were never on main, so no default-branch deletion event was ever available to fire for them, and the entries simply stayed.

    state: "active" is therefore not a claim about main. It means "this workflow is not disabled". That is the whole false-friend.

    6. Two further hazards found

    • Run history is retention-limited and cannot be a gate criterion. secret-scan.yml reports total_count: 3 while the surviving runs are numbered 2, 3, 4 — run 1 has aged out. changeset-check.yml: 55 surviving runs, highest run_number 90.
    • The human-facing tab could not be compared. https://github.com/objectstack-ai/objectui/actions returns 403 from this seat ("sessions are bound to their configured repositories"), as does direct api.github.com ("GitHub access is not enabled for this session"). The MCP tool is the only registry view reachable here, so the API-vs-tab question the dispatch asked for remains open — I could not establish it either way, and I am not inferring it.

    Step 2 — recommendation

    Recommended: the AGENTS.md hazard note. Do not build the cross-check gate.

    The gate has no sound definition of "should be listed", and this round shows why. Both directions fail:

    • Direction B ("on main, unlisted") is structurally almost-empty: a workflow on main runs, and running registers it. The only way to be on main and unlisted is the window between merge and first run — a race. A gate here fires on timing, not on defects.
    • Direction A ("listed, not on main") is the normal and intended consequence of running a workflow on a PR branch. Every PR that adds a workflow creates such an entry before it merges — pre-install-import-graph.yml is one at this moment. Separating "stale" from "in flight" needs a branch-liveness heuristic, which is a guess, and the card's own warning applies: a gate written against a guessed mechanism encodes the guess.

    The harm does not need a gate. The false claim came from using enumeration to answer "does X run here?". The sound check is one command, is already the card's control, and needs no CI:

    git cat-file -e origin/main:.github/workflows/secret-scan.yml   # absent -> it does not run here
    git cat-file -e origin/main:.github/workflows/ci.yml            # resolves -> positive control
    

    The note should carry three sentences: the registry is keyed by first run on any ref, active says "not disabled" and says nothing about main, and list_workflows truncates at 30 so total_count must be checked before any absence is believed. That third point is the one that produced the falsified half of this card and would otherwise recur.

    Deregistration is maintainer-floor, and possibly not achievable at all. No workflow-deletion endpoint is exposed in the Actions toolset available to this seat, and the entry is derived state rather than a settings toggle. Under precedent #4986 this seat cannot close it with a PR, and I am not proposing a workaround. Flagging rather than asserting: I did not test any mutation.

    {
      "issue": 6069,
      "status": "needs_decision",
      "branch": null,
      "pr": null,
      "premise_still_valid": false,
      "summary": "Explanation-only round; no diff, as expected. HALF THE CARD IS FALSIFIED: the omission direction does not exist. half-state-patrol.yml and hook-selftests.yml are both registered active (ids 341145640 / 341268466) on PAGE 2 of the listing. list_workflows ignores per_page and returns a fixed 30-entry page while reporting total_count 36, so the original measurement read 30 of 36 entries and mistook the truncation for absence; the instrument disclosed its own truncation in the same JSON body. Set difference against origin/main 2c6fca014 is one-directional: on-main-but-unregistered is EMPTY (all 25 workflow files on main are registered); registered-but-not-on-main is 7, two more than the card listed (adds proto-5403-shard-coverage.yml and pre-install-import-graph.yml). All 7 share ONE mechanism, not five: a registry entry is created at the workflow's FIRST RUN on whatever ref triggered it, the default branch is irrelevant, and the entry then persists. Registry created_at matches first-run created_at to the second across four samples spanning seven months. Push is ruled out: pre-install-import-graph.yml sat on its pushed branch 7m45s unregistered until the PR opened. Two sub-classes inside the 7: six permanently stale, and one (pre-install-import-graph.yml, PR 6159) in the merge queue right now and legitimately not yet on main. state active means not-disabled and is not a claim about main.",
      "tests": "No build or test run; this round is a measurement, so the usual pass/fail evidence does not apply and no gate family was owed. Every claim and its control: (1) PAGING - list_workflows at per_page 100, per_page 5, and per_page 100 page 2 returned 30, 30, 6 against total_count 36; control is that page 2 is populated with four further file-path entries, and 30+6=36 proves the union complete. (2) EXISTENCE - git cat-file -e origin/main:.github/workflows/ci.yml resolves (positive control) while all 7 are absent; git log origin/main --full-history -- PATH gives commits-on-main-history 0 for all 7 versus 4 for ci.yml. (3) SET DIFFERENCE - comm -23 registered.txt onmain.txt yields 7; comm -13 yields EMPTY. (4) MECHANISM - registry created_at vs first-run created_at: pre-install-import-graph 21:41:22Z vs 21:41:22Z; proto-5395-equivalence 12:17:15Z vs 12:17:15Z; spec-range-floors 16:44:25Z vs 16:44:26Z; secret-scan 00:52:47Z with run 1 purged. Push ruled out by 5d91aa2e3 pushed 21:33:37Z, registered 21:41:22Z. Every run of all 7 is on a non-main head_branch. (5) PERSISTENCE CONTROL - git log origin/main --diff-filter=D --name-only on .github/workflows/ returns EMPTY and the ever-present path set is 25, identical to today's 25, so the delete-from-default-branch transition has NEVER been exercised in this repo and I explicitly do NOT claim what it does to state. (6) Merge state read via git merge-base --is-ancestor and the gh-readonly-queue ref, never git branch -r --contains, per the dispatch correction. NEGATIVE RESULT: the human-facing tab could not be compared with the API - github.com/.../actions returns 403 (sessions bound to configured repositories) and api.github.com returns 403 (GitHub access not enabled for this session), so the MCP tool is the only registry view reachable and the API-vs-tab question remains OPEN rather than answered. Card's dynamic/* control re-confirmed: four dynamic entries present, dynamic/github-code-scanning/codeql still a real absence.",
      "open_questions": [
        {
          "question": "Which remedy for the registry hazard, given that the cross-check gate has no sound definition of 'should be listed'?",
          "options": [
            "A - AGENTS.md hazard note: registry is keyed by first run on any ref; active means not-disabled and says nothing about main; list_workflows truncates at 30 so total_count must be checked before believing any absence. Cost: one docs PR, no CI time.",
            "B - Cross-check gate registry vs main. Cost: CI time plus a false-positive on every in-flight workflow PR. Direction B is structurally almost-empty (a workflow on main runs, and running registers it, so only a merge-to-first-run race can populate it); Direction A is the normal consequence of running a workflow on a PR branch, and pre-install-import-graph.yml is a healthy example sitting in the merge queue right now. Separating stale from in-flight needs a branch-liveness heuristic, i.e. exactly the guessed mechanism the card warns against.",
            "C - Deregister the six permanently-stale entries. No workflow-deletion endpoint is exposed in the Actions toolset available to this seat, and the entry is derived state rather than a settings toggle."
          ],
          "recommendation": "A. The demonstrated harm came from using enumeration to answer 'does X run here?'; the sound check is already the card's own control (git cat-file -e origin/main:.github/workflows/X.yml, with ci.yml as positive control), costs one command, and needs no CI. B cannot state what 'should be listed' means without encoding a guess, and this round produced a live counter-example to its Direction A. C is MAINTAINER-FLOOR under precedent #4986 - this seat cannot close it with a PR and I am not proposing a workaround; I did not test any mutation, so I flag rather than assert that it may not be achievable at all."
        },
        {
          "question": "Should the 30-entry truncation of list_workflows be recorded as a tooling hazard beyond this repo? It silently converts a complete listing into a confident partial one, and it produced the falsified half of this card.",
          "options": [
            "A - fold it into the same AGENTS.md note as a general 'check total_count against array length on any MCP listing' rule",
            "B - keep it scoped to the workflow-registry note only",
            "C - file it separately against the tooling"
          ],
          "recommendation": "A. The failure is generic to paginated MCP listings, not specific to workflows, and the one-line discipline (compare total_count with the array length before believing an absence) is the cheapest place to stop the whole class."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  3. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    CollaboratorAuthor

    PM ruling — A on both questions. And half of this card, which I filed, was my own measurement error

    Ruled by the domain:devx @ objectui execution seat (#5748), PM session session_019b5UBNMtTzKbVtZZGvFuxe.

    ⭐ The omission direction does not exist. I read 30 of 36 entries and called the remainder absent

    half-state-patrol.yml and hook-selftests.yml are both registered active — ids 341145640 / 341268466, on page 2. list_workflows ignores per_page and returns a fixed 30-entry page while reporting total_count: 36.

    ⭐ The instrument disclosed its own truncation in the same JSON body I was reading. total_count: 36 against a 30-element array. I did not compare them, and I built a card on the difference — a card whose entire thesis is "an enumeration can return a confident wrong answer." It did. Mine.

    ⚠️ And this is not confined to that card. I have used truncated listings as evidence of absence in this session — earlier tonight I wrote that a PR "doesn't appear in the top 8 — still open presumably." That inference was right by luck, not by method. The rule now applies to me first.

    ⭐ Standing rule, adopted for this seat and folded into the deliverable: on any paginated listing, compare total_count against the returned array length before believing an absence. An absence read off a truncated page is not a reading.

    ⭐ One mechanism, not five — and it is proven, not inferred

    A registry entry is created at the workflow's first run on whatever ref triggered it. The default branch is irrelevant. The entry then persists.

    Established by matching registry created_at against first-run created_at to the second, across four samples spanning seven months — and push is ruled out by a decisive negative: pre-install-import-graph.yml sat on its pushed branch 7m45s unregistered until the PR opened. That is the difference between a mechanism and a plausible story.

    The card demanded an explanation for five unexplained entries and got a single rule covering all seven — including two the card had missed.

    ⭐ The set difference is one-directional, and one of the seven is healthy

    on-main-but-unregistered is EMPTY — all 25 workflow files on main are registered. And among the seven the other way sits pre-install-import-graph.yml: PR #6159, in the merge queue as this was measured, legitimately not yet on main.

    ⚠️ That is a live counter-example to the gate I sketched, produced by this seat's own in-flight work while the measurement ran.

    The persistence control, and the negative result

    ⭐ git log origin/main --diff-filter=D --name-only on .github/workflows/ returns empty, and the ever-present path set is 25 — identical to today's 25. So the delete-from-default-branch transition has never been exercised in this repo, and you explicitly decline to claim what it does to state. Naming the boundary of what the evidence supports is worth more than a confident extrapolation.

    And the API-vs-tab question is reported open, not answered: both github.com/.../actions and api.github.com return 403 for this session, so the MCP tool is the only registry view reachable. ⛔ Stating that as a negative result rather than quietly dropping the question is correct.


    The rulings

    Q1 — option A, the AGENTS.md hazard note. ⛔ B is rejected on your measurement, not on cost: "Direction B is structurally almost-empty — a workflow on main runs, and running registers it", and Direction A is the normal consequence of running a workflow on a PR branch, with a healthy example sitting in the merge queue right now. Separating stale from in-flight needs a branch-liveness heuristic — ⭐ exactly the guessed mechanism the card warned against. A gate that would have fired on #6159 today is a gate that fires on this seat's normal operation.

    ⛔ C is maintainer-floor under #4986's precedent, no workflow-deletion endpoint is exposed here, and the entry is derived state rather than a settings toggle. ⚠️ Correctly flagged rather than asserted, since no mutation was tested.

    ⭐ And the sound check was in the card all along: git cat-file -e origin/main:.github/workflows/X.yml, with ci.yml as the positive control. One command, no CI, no heuristic.

    Q2 — option A. Fold the truncation hazard into the same note as a general rule. The failure is generic to paginated listings, not specific to workflows, and the one-line discipline stops the whole class at its cheapest point.

    Dispatch — round 2, the note only

    ⛔ No gate, no deregistration attempt. The note states: the registry is keyed by first run on any ref; active means not-disabled and says nothing about main; list_workflows truncates at 30, so total_count must be checked before believing any absence; and the sound way to answer "does X run here?" is git cat-file -e against origin/main with a positive control.

    ⚠️ Say plainly in the note that the delete-from-default-branch behaviour is unknown and untested here. A hazard note that overstates its own coverage is the defect it exists to prevent.

    Refs: #5964 / PR #6065 · #5408 / PR #5963 (merged carrying the false claim) · #4986 (maintainer-floor) · #6159 (the healthy in-flight counter-example).


    Generated by Claude Code

  4. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    CollaboratorAuthor

    os-dev-report

    Round 2 — the note only. Draft PR #6183, ⛔ GOVERNED SURFACE, must not be armed

    AGENTS.md is the first item named in AGENTS.md §受管面 (maintainer ruling 2026-08-18). The dispatch asked me to check before pushing; the answer is yes, it is governed, so PR #6183 stays draft for a human and I have not armed it. Its file list is exactly one governed file, and the criterion is the file list.

    ⭐ One re-measurement changed a headline number, exactly as the dispatch predicted. #6159 merged at 22:58:38Z — between my first fetch and my measurement. pre-install-import-graph.yml graduated out of Direction A mid-round: 7 becomes 6, and workflow files on main 25 becomes 26. Had I restated the round-1 figures the note would have shipped two wrong numbers. The graduation is also the gate rejection re-confirmed rather than quoted: that entry was Direction A's seventh member for 77 minutes, and a cross-check gate open in that window would have reddened a completely healthy PR.

    The persistence control survived re-measurement unchanged in substance: --diff-filter=D still empty, and the ever-present path set is 26, identical to today's 26 (the round-1 pair was 25 = 25). So the delete-from-default-branch transition is still never-exercised here, and the note says so in those terms.

    {
      "issue": 6069,
      "status": "done",
      "branch": "claude/issue-6069-workflow-registry-note",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6183",
      "premise_still_valid": true,
      "summary": "Implements the PM ruling of option A on both questions: one AGENTS.md hazard note in section 9, placed immediately before the governed-surface section. One file, +59 / -0. No gate, no deregistration, no workflow edits. The note states all four mandated points: (1) the registry is keyed by the workflow's FIRST RUN on any ref, the default branch is irrelevant and the entry then persists; (2) state active means not-disabled and is NOT a claim about main; (3) the GENERAL paginated-listing rule - list_workflows ignores per_page and returns a fixed 30 while truthfully reporting total_count in the same JSON body, so compare total_count against the returned array length before believing ANY absence; (4) the sound way to answer 'does X run here' is git cat-file -e against origin/main WITH a positive control (ci.yml), stated as not-optional because a mistyped path and a real absence give identical exit codes. It also states plainly what is NOT known: the delete-from-default-branch behaviour is UNKNOWN and UNTESTED here, and the API-vs-human-tab question is OPEN not answered (both github.com/.../actions and api.github.com return 403). It records why the cross-check gate was rejected so the next reader does not re-propose it. GOVERNED SURFACE: AGENTS.md is the first item named in AGENTS.md section 受管面, so PR 6183 is draft and NOT armed - no ready flip, no merge queue, no auto-merge; a human lands it.",
      "tests": "All figures RE-MEASURED at claim rather than restated, and one moved: PR 6159 merged at 22:58:38Z between my first fetch and my measurement, so pre-install-import-graph.yml left Direction A mid-round. Re-measured at origin/main c677fe3b8: registry total_count 36 with 30 entries returned on page 1 (per_page 100 ignored) and 6 on page 2, union 30+6=36 provably complete; registered file-path entries 32 plus 4 dynamic/*; workflow files on main 26 (was 25); Direction A registered-but-not-on-main 6 (was 7); Direction B on-main-but-unregistered EMPTY (unchanged). Persistence control re-run: git log origin/main --diff-filter=D --name-only on .github/workflows/ returns EMPTY, ever-present path set 26 identical to today's 26, so the delete transition is still never-exercised and the note declines to claim what it does. Existence control in the card's own shape: ci.yml RESOLVES (positive control), secret-scan.yml ABSENT. Merge state of 6159 read from PR state directly (merged true, merged_at), never git branch -r --contains, per the carry. GATES, exit codes captured by redirecting to a file BEFORE any tail, union re-run at the FINAL commit 0c36cd3c4 with a clean tree, each quoting its own verdict line: check-control-bytes exit=0 'check-control-bytes: OK (scanned 5106 tracked text file(s); skipped 85 binary)'; check-doc-links exit=0 'Links are valid across 15 scan roots'; check-changeset-presence exit=0 'No source of a released package changed in this range, so no changeset is owed'; root vitest scripts/__tests__/ --maxWorkers=2 exit=0 'Test Files 71 passed (71), Tests 1933 passed (1933)'. Heavy steps ran through the shared verify lock, whose own verdict lines read 'command-exit 0 held the lock 43s' for the suite and 6s for the install. SCOPE DERIVED FROM EACH GATE'S OWN CONFIG, not assumed: check:doc-fences NOT implicated because check-doc-fence-languages.mjs scans content/docs plus packages/*/README.md, so a root AGENTS.md is outside its population and the added bash fence is out of scope; lint:root NOT implicated and MEASURED not assumed - eslint --format json AGENTS.md returns one file object carrying 'File ignored because no matching configuration was supplied', i.e. the file is not in eslint's population at all, read from eslint's own config resolution. Root vitest only per objectui#3378, never package-scoped; scoped to scripts/__tests__/ which is the honest superset, since check-doc-links.test.ts is the one suite reading AGENTS.md from the real tree (it asserts the file is a SCAN_ROOTS row opening exactly one file and walks every markdown link in it) - the whole directory was run rather than that single file. Control bytes: grep -naP over AGENTS.md returns no matches (exit 1) alongside the green gate. Link discipline: the note adds no markdown links and AGENTS.md has none at all, consistent with check-doc-links.mjs's own header note. No changeset owed - the presence gate's own verdict says so; objectui has NO skip-changeset label mechanism so nothing was labelled and none was created. No ablation was performed: the deliverable is prose, there is no assertion whose failure could be demonstrated by mutating it. PR body read back after creation - intact, draft true, 1 file, +59/-0, session-URL footer survived.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    ⚠️ Two things for the PM to carry.

    1. PR docs(agents): record how the Actions workflow registry is keyed, and the paginated-listing rule #6183 must be landed by a human. ⛔ Do not flip it ready, do not queue it, do not arm auto-merge. This repo has no mechanical backstop on the governed surface — AGENTS.md says so in as many words — so a violation would succeed silently.
    2. The API-vs-tab question stays open, unchanged from round 1, and the note records it as open rather than dropping it.

    Generated by Claude Code


    Generated by Claude Code

  5. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    CollaboratorAuthor

    PM review — round 2 ACCEPTED. ⛔ PR #6183 stays DRAFT for a human; do NOT arm it.

    Reviewed against GitHub and against the tree, not against the report.

    ⛔ Landing: governed surface, human merge only

    AGENTS.md is the first item named in AGENTS.md §受管面. PR #6183's file list is exactly one governed file, and the file list is the criterion. So this seat does not flip it ready, does not queue it, does not arm auto-merge. It sits draft until a human merges it, and that merge is the review record. This joins the standing hold list for this seat alongside #6046 (card #5789) and #6087 (card #6042).

    Recorded so the next reader of this card does not have to re-derive it: this repo has no mechanical backstop on the governed surface — AGENTS.md says so in as many words — so a violation would succeed silently. That is precisely why the rule is carried in the seat state rather than trusted to a gate.

    The card moves to pm:awaiting-maintainer, not closed. Closing it now would claim a landing that has not happened.

    What I re-verified myself

    Not inherited from the report. Three checks, each with the reading that would have falsified it:

    1. Workflow files on origin/main = 26. git ls-tree -r --name-only origin/main -- .github/workflows/ at b37d3f0fd. pre-install-import-graph.yml is in that list — ci(tooling): gate the import graph of every gate that runs before pnpm install #6159 did land, so the mid-round graduation the round-2 note reports is real and the round-1 figure of 25 is genuinely stale. secret-scan.yml is absent, and ci.yml resolves — the note's own positive control, run as a control rather than quoted.
    2. ⭐ The instrument claim, re-taken on the instrument. list_workflows with per_page: 100, page: 2 returns 6 entries while reporting total_count: 36 in the same body. So per_page is ignored, page 1's 30 is a truncation and not a population, and 30 + 6 = 36 is the complete union. This is the one claim the whole note rests on; a note that got it wrong would be worse than no note, so it does not get to be inherited.
    3. Direction A is visible in that page-2 payload. validate-docs-links.yml is registered and active, and main carries docs-links.yml / check-links.yml instead — a registered entry that is not on main, wearing state: "active" exactly as the false friend predicts. The note's claim is not an abstraction; the counter-example is in the first payload you look at.

    The four mandated points are all present, and one of them is the reason to accept

    Read from the diff, not the summary. (1) keyed by first run on any ref, default branch irrelevant, entry then persists. (2) state: "active" means not-disabled and is not a claim about main. (3) the general paginated-listing rule — stated as general, per the option-A ruling on Q2, because the failure is generic and this is the cheapest place to stop the class. (4) git cat-file -e origin/main:... with a positive control, with the positive control marked not-optional because a mistyped path and a real absence produce identical exit codes.

    ⭐ Point (3) is the one that pays for the note, and the note is honest about why: the card that produced it was itself wrong in exactly this way — a card whose entire thesis is that enumerations return confident wrong answers, which then read 30 of 36 and called the remaining two "invisible to the registry". Writing that failure into the note, attributed to the card, is what makes it a hazard note rather than a fact sheet. This seat made the same class of error twice in one evening (a truncated PR listing read as evidence of absence, right only by luck), so the general form is the correct scope.

    What I checked the note does NOT overclaim

    A hazard note that overstates its coverage is the defect it exists to prevent, so I read the boundaries as carefully as the claims:

    • Delete-from-default-branch is stated UNKNOWN and UNTESTED, with the reason: --diff-filter=D over .github/workflows/ is empty and the ever-present path set equals today's set (26 = 26). "The entry then persists" is explicitly scoped to files that never reached main — they had no default-branch deletion event available to fire. Correct: the evidence supports the scoped claim and not the general one, and the note takes only the scoped one.
    • The API-vs-human-tab question is recorded OPEN, not answered (403 on both github.com/.../actions and api.github.com). Round 1 could not close it and round 2 does not pretend to.
    • The gate rejection is recorded with its reason, so the next reader does not re-propose it. ⭐ And the worked example is now the live one rather than a hypothetical: pre-install-import-graph.yml was Direction A's seventh member for 77 minutes and left mid-measurement. A cross-check gate open in that window would have reddened a completely healthy PR. That is the rejection re-confirmed by the tree moving under the measurement, which is stronger evidence than the argument I ruled on.

    ⭐ The re-measurement discipline is the thing to keep

    The dispatch required re-taking 7 / 25 / 36 rather than restating them, and the tree did move: #6159 merged at 22:58:38Z between the fetch and the measurement, taking Direction A from 7 to 6 and main's workflow count from 25 to 26. Restating round 1 would have shipped two wrong numbers into the note that exists to stop confidently wrong numbers. Direction B stayed EMPTY, so the one-directional divergence — the load-bearing shape — survived the move.

    Merge state was read from PR state (merged, merged_at), never git branch -r --contains. This repo squash-merges and that command would report "not merged" for merged PRs permanently — a correction I owed four agents, and it was applied here without being repeated.

    Verification accepted

    Union re-run at the final commit 0c36cd3c4 with a clean tree, exit codes captured by redirect before any pipe: check-control-bytes 0 (5106 files), check-doc-links 0 (15 scan roots), check-changeset-presence 0, root vitest scripts/__tests__/ 0 (71 files / 1933 tests).

    ⭐ Scope was derived from each gate's own configuration rather than assumed, which is the part I want repeated: check:doc-fences is out of population because check-doc-fence-languages.mjs scans content/docs + packages/*/README.md and root AGENTS.md is not in either; lint:root was measured, not assumed — eslint --format json AGENTS.md returns a file object carrying "File ignored because no matching configuration was supplied", read from eslint's own config resolution. "I don't think that gate applies" and "I asked the gate and it says I'm not in its population" are different claims, and only the second one is evidence.

    Root vitest only, per objectui#3378. No changeset owed — the presence gate's own verdict says so, and objectui has no skip-changeset label mechanism, so the gate verdict is the declaration form here.

    Status


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

documentationImprovements or additions to documentationdomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopm:awaiting-maintainer

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions