docs(ci): skills-paths header names both scan roots and drops the file count - #7412

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header
Sep 2, 2026
Merged

docs(ci): skills-paths header names both scan roots and drops the file count#7412
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7404

The header comment of .github/workflows/skills-paths.yml stated the gate's scan surface as skills/** over 18 markdown files. Both halves were stale, and they drifted in two separate steps. Prose only: the parsed workflow document is byte-identical before and after (proof in the gate table).

A reader who took the old parenthetical literally would conclude .claude/skills is ungated — the exact failure mode the widening exists to remove.

Before / after

 # Why this is its own workflow instead of a step in `ci.yml` or `lint.yml`: this
-# gate's ENTIRE scan surface is markdown (`skills/**`, guide prose), and both of-# those workflows list `'**/*.md'`, `content/**` and `docs/**` under the+# gate's ENTIRE scan surface is markdown (`skills/**` and `.claude/skills/**`,+# guide prose — the roots are `SCAN_ROOTS` in the script), and both of those+# workflows list `'**/*.md'`, `content/**` and `docs/**` under the
# `paths-ignore` of their `push` trigger, with no per-job path filter available in
-# It needs no install and no build — a checkout plus one `node` call over 18-# markdown files, a couple of seconds — so keep it that way if you add checks to-# it.+# It needs no install and no build — a checkout plus one `node` call over the+# markdown under those roots, a couple of seconds — so keep it that way if you+# add checks to it. No file count is stated on purpose: it goes stale on every+# guide added, which is how the previous one drifted.

The enumeration now names both roots and points at the declaration rather than copying a list; the count is dropped rather than restated at 20.

The roots, as the script declares them

scripts/check-skills-paths.mjs:154, verbatim:

exportconstSCAN_ROOTS=['skills','.claude/skills'];

Landed in 7c77298"chore(scripts): check-skills-paths scans .claude/skills too — 27 to 88 stated paths under the gate, the #7251 pins restored (#7358) (#7407)". It replaced export const SCAN_ROOT = 'skills'.

The gate's own run at this head prints the surface, which is where the 18/16/20 arithmetic resolves:

✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
skills/ — 27/27 resolve across 16 file(s)
.claude/skills/ — 60/61 resolve across 4 file(s)

16 under skills/ (was 18 before the guide move) plus 4 under .claude/skills/ = the 20 the card names.

The three claims kept, each verified on main at 6aeba67

1. The scan surface is entirely markdown.scripts/check-skills-paths.mjs:202 — the walker admits nothing else:

elseif(entry.name.endsWith('.md'))files.push(full);

Worth stating precisely, because it is a claim about what the gate reads, not about what the directories hold: the roots also contain 11 non-markdown files (skills/objectui/evals/*.json), which the gate never opens. The comment's wording ("scan surface") is already the accurate one, so it is kept as-is.

2. ci.yml and lint.yml both list '**/*.md' under the paths-ignore of their push trigger.ci.yml:6-11:

push:
branches: [main, develop]paths-ignore:
- '**/*.md'
- 'content/**'
- 'docs/**'
- 'apps/site/**'
- '.changeset/**'

lint.yml:32-36 carries the same list minus apps/site/**. The header's scoping to the push trigger is exact and still required: both files removed paths-ignore from their pull_request trigger (ci.yml:12, lint.yml:37 — "No paths-ignore here any more (objectui#3523, step 2)") and moved that decision into the jobs. Unchanged.

3. This workflow carries no paths or paths-ignore of its own. After comment-stripping there is no such key at all — the YAML parse below enumerates the whole on: block, and scripts/__tests__/check-skills-paths.test.ts:552-555 pins it in both directions.

That pin is also why a comment edit cannot move anything in it. scripts/__tests__/check-skills-paths.test.ts:517-526, the yamlOf helper, drops whole-line comments before every assertion:

.filter((line)=>!/^\s*#/.test(line))

Its own doc comment gives the reason: "this file's own header discusses paths and paths-ignore in prose ... A scan that counted comments would report filters and duplicate homes that no file has."

Why the count is dropped, not restated

lint.yml:26-28 records the same lesson from the other side, about a number that went stale on its own clock: "The order of magnitude is the whole argument; the integer never was." Restating 20 would re-arm the same drift on the next guide added.

Gates — all at head 54d5db8, exit code captured by redirect before any pipe

GateExitIts own verdict line
vitest run scripts/__tests__/check-skills-paths.test.ts (+ 2 derived, below)0Test Files 3 passed (3) · Tests 88 passed (88); lock: VERDICT command-exit 0
node scripts/check-skills-paths.mjs0✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
node scripts/check-doc-links.mjs0Links are valid across 17 scan roots.
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 6108 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs0✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test .github/workflows/skills-paths.yml0✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.
YAML parse, yaml@2.9.0 from node_modules0VERDICT: PARSED DOCUMENT IDENTICAL before/after — comment-only change confirmed

The changeset gate's verdict decides, and it says none is owed: one file changed, zero of them published source.

The YAML check parses both revisions with the repo's own yaml@2.9.0 and compares the parsed documents, so "comment-only" is measured rather than asserted. It also re-prints on: (pull_request, push, merge_group, workflow_dispatch — no paths key), permissions: contents: read, and the three steps unchanged.

Two suites beyond the dispatched list, derived from the actual diff — both assert on this file, so a header edit is in their blast radius:

  • scripts/__tests__/check-pre-install-import-graph.test.ts:262 pins 'skills-paths.yml : skills-paths -> scripts/check-skills-paths.mjs'
  • scripts/__tests__/merge-queue-reporting.test.ts:91 pins skills-paths.yml among the workflows that must subscribe merge_group

Both are in the 3-file, 88-test run above.

Premise notes

Everything in the card held. Three deviations from the dispatch brief, none of them blocking:

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…e count
The header stated the gate's scan surface as `skills/**` over 18 markdown
files. Both halves were stale, and they drifted in two separate steps: the
guide move took the count under `skills/` from 18 to 16, and widening
`SCAN_ROOTS` to `['skills', '.claude/skills']` made the surface 20 files
across two roots.
A reader who took the parenthetical literally would conclude `.claude/skills`
is ungated — the exact failure mode the widening exists to remove. The
enumeration now names both roots and points at `SCAN_ROOTS` as the authority;
the count is dropped rather than restated, because it goes stale on every
guide added. `lint.yml`'s header records the same lesson about a number that
went stale on its own clock.
Comment-only: no change to triggers, jobs, steps, permissions or path
filters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-litantClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (skills lane seat, session session_01LraLgQVGq8egUwfYZpbYt1): in-seat ACCEPT on #7404 (comment 5516027756); NOT GOVERNED per check-governed-queue-guard --test; every check on 54d5db8 green by 20:47Z. Flipped ready and enqueued with auto-merge (squash) per landing-operations B. At MERGED the seat strips pm:dispatched on #7404.


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 5e9b189Sep 2, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-7404-skills-paths-workflow-header branch September 2, 2026 21:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(ci): skills-paths.yml's header states the gate's scan surface as skills/** over 18 files — both halves are stale

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(ci): skills-paths header names both scan roots and drops the file count - #7412

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header
Sep 2, 2026
Merged

docs(ci): skills-paths header names both scan roots and drops the file count#7412
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7404

The header comment of .github/workflows/skills-paths.yml stated the gate's scan surface as skills/** over 18 markdown files. Both halves were stale, and they drifted in two separate steps. Prose only: the parsed workflow document is byte-identical before and after (proof in the gate table).

A reader who took the old parenthetical literally would conclude .claude/skills is ungated — the exact failure mode the widening exists to remove.

Before / after

 # Why this is its own workflow instead of a step in `ci.yml` or `lint.yml`: this
-# gate's ENTIRE scan surface is markdown (`skills/**`, guide prose), and both of-# those workflows list `'**/*.md'`, `content/**` and `docs/**` under the+# gate's ENTIRE scan surface is markdown (`skills/**` and `.claude/skills/**`,+# guide prose — the roots are `SCAN_ROOTS` in the script), and both of those+# workflows list `'**/*.md'`, `content/**` and `docs/**` under the
# `paths-ignore` of their `push` trigger, with no per-job path filter available in
-# It needs no install and no build — a checkout plus one `node` call over 18-# markdown files, a couple of seconds — so keep it that way if you add checks to-# it.+# It needs no install and no build — a checkout plus one `node` call over the+# markdown under those roots, a couple of seconds — so keep it that way if you+# add checks to it. No file count is stated on purpose: it goes stale on every+# guide added, which is how the previous one drifted.

The enumeration now names both roots and points at the declaration rather than copying a list; the count is dropped rather than restated at 20.

The roots, as the script declares them

scripts/check-skills-paths.mjs:154, verbatim:

exportconstSCAN_ROOTS=['skills','.claude/skills'];

Landed in 7c77298"chore(scripts): check-skills-paths scans .claude/skills too — 27 to 88 stated paths under the gate, the #7251 pins restored (#7358) (#7407)". It replaced export const SCAN_ROOT = 'skills'.

The gate's own run at this head prints the surface, which is where the 18/16/20 arithmetic resolves:

✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
skills/ — 27/27 resolve across 16 file(s)
.claude/skills/ — 60/61 resolve across 4 file(s)

16 under skills/ (was 18 before the guide move) plus 4 under .claude/skills/ = the 20 the card names.

The three claims kept, each verified on main at 6aeba67

1. The scan surface is entirely markdown.scripts/check-skills-paths.mjs:202 — the walker admits nothing else:

elseif(entry.name.endsWith('.md'))files.push(full);

Worth stating precisely, because it is a claim about what the gate reads, not about what the directories hold: the roots also contain 11 non-markdown files (skills/objectui/evals/*.json), which the gate never opens. The comment's wording ("scan surface") is already the accurate one, so it is kept as-is.

2. ci.yml and lint.yml both list '**/*.md' under the paths-ignore of their push trigger.ci.yml:6-11:

push:
branches: [main, develop]paths-ignore:
- '**/*.md'
- 'content/**'
- 'docs/**'
- 'apps/site/**'
- '.changeset/**'

lint.yml:32-36 carries the same list minus apps/site/**. The header's scoping to the push trigger is exact and still required: both files removed paths-ignore from their pull_request trigger (ci.yml:12, lint.yml:37 — "No paths-ignore here any more (objectui#3523, step 2)") and moved that decision into the jobs. Unchanged.

3. This workflow carries no paths or paths-ignore of its own. After comment-stripping there is no such key at all — the YAML parse below enumerates the whole on: block, and scripts/__tests__/check-skills-paths.test.ts:552-555 pins it in both directions.

That pin is also why a comment edit cannot move anything in it. scripts/__tests__/check-skills-paths.test.ts:517-526, the yamlOf helper, drops whole-line comments before every assertion:

.filter((line)=>!/^\s*#/.test(line))

Its own doc comment gives the reason: "this file's own header discusses paths and paths-ignore in prose ... A scan that counted comments would report filters and duplicate homes that no file has."

Why the count is dropped, not restated

lint.yml:26-28 records the same lesson from the other side, about a number that went stale on its own clock: "The order of magnitude is the whole argument; the integer never was." Restating 20 would re-arm the same drift on the next guide added.

Gates — all at head 54d5db8, exit code captured by redirect before any pipe

GateExitIts own verdict line
vitest run scripts/__tests__/check-skills-paths.test.ts (+ 2 derived, below)0Test Files 3 passed (3) · Tests 88 passed (88); lock: VERDICT command-exit 0
node scripts/check-skills-paths.mjs0✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
node scripts/check-doc-links.mjs0Links are valid across 17 scan roots.
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 6108 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs0✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test .github/workflows/skills-paths.yml0✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.
YAML parse, yaml@2.9.0 from node_modules0VERDICT: PARSED DOCUMENT IDENTICAL before/after — comment-only change confirmed

The changeset gate's verdict decides, and it says none is owed: one file changed, zero of them published source.

The YAML check parses both revisions with the repo's own yaml@2.9.0 and compares the parsed documents, so "comment-only" is measured rather than asserted. It also re-prints on: (pull_request, push, merge_group, workflow_dispatch — no paths key), permissions: contents: read, and the three steps unchanged.

Two suites beyond the dispatched list, derived from the actual diff — both assert on this file, so a header edit is in their blast radius:

  • scripts/__tests__/check-pre-install-import-graph.test.ts:262 pins 'skills-paths.yml : skills-paths -> scripts/check-skills-paths.mjs'
  • scripts/__tests__/merge-queue-reporting.test.ts:91 pins skills-paths.yml among the workflows that must subscribe merge_group

Both are in the 3-file, 88-test run above.

Premise notes

Everything in the card held. Three deviations from the dispatch brief, none of them blocking:

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…e count
The header stated the gate's scan surface as `skills/**` over 18 markdown
files. Both halves were stale, and they drifted in two separate steps: the
guide move took the count under `skills/` from 18 to 16, and widening
`SCAN_ROOTS` to `['skills', '.claude/skills']` made the surface 20 files
across two roots.
A reader who took the parenthetical literally would conclude `.claude/skills`
is ungated — the exact failure mode the widening exists to remove. The
enumeration now names both roots and points at `SCAN_ROOTS` as the authority;
the count is dropped rather than restated, because it goes stale on every
guide added. `lint.yml`'s header records the same lesson about a number that
went stale on its own clock.
Comment-only: no change to triggers, jobs, steps, permissions or path
filters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-litantClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (skills lane seat, session session_01LraLgQVGq8egUwfYZpbYt1): in-seat ACCEPT on #7404 (comment 5516027756); NOT GOVERNED per check-governed-queue-guard --test; every check on 54d5db8 green by 20:47Z. Flipped ready and enqueued with auto-merge (squash) per landing-operations B. At MERGED the seat strips pm:dispatched on #7404.


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 5e9b189Sep 2, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-7404-skills-paths-workflow-header branch September 2, 2026 21:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(ci): skills-paths.yml's header states the gate's scan surface as skills/** over 18 files — both halves are stale

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(ci): skills-paths header names both scan roots and drops the file count - #7412

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header
Sep 2, 2026
Merged

docs(ci): skills-paths header names both scan roots and drops the file count#7412
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7404

The header comment of .github/workflows/skills-paths.yml stated the gate's scan surface as skills/** over 18 markdown files. Both halves were stale, and they drifted in two separate steps. Prose only: the parsed workflow document is byte-identical before and after (proof in the gate table).

A reader who took the old parenthetical literally would conclude .claude/skills is ungated — the exact failure mode the widening exists to remove.

Before / after

 # Why this is its own workflow instead of a step in `ci.yml` or `lint.yml`: this
-# gate's ENTIRE scan surface is markdown (`skills/**`, guide prose), and both of-# those workflows list `'**/*.md'`, `content/**` and `docs/**` under the+# gate's ENTIRE scan surface is markdown (`skills/**` and `.claude/skills/**`,+# guide prose — the roots are `SCAN_ROOTS` in the script), and both of those+# workflows list `'**/*.md'`, `content/**` and `docs/**` under the
# `paths-ignore` of their `push` trigger, with no per-job path filter available in
-# It needs no install and no build — a checkout plus one `node` call over 18-# markdown files, a couple of seconds — so keep it that way if you add checks to-# it.+# It needs no install and no build — a checkout plus one `node` call over the+# markdown under those roots, a couple of seconds — so keep it that way if you+# add checks to it. No file count is stated on purpose: it goes stale on every+# guide added, which is how the previous one drifted.

The enumeration now names both roots and points at the declaration rather than copying a list; the count is dropped rather than restated at 20.

The roots, as the script declares them

scripts/check-skills-paths.mjs:154, verbatim:

exportconstSCAN_ROOTS=['skills','.claude/skills'];

Landed in 7c77298"chore(scripts): check-skills-paths scans .claude/skills too — 27 to 88 stated paths under the gate, the #7251 pins restored (#7358) (#7407)". It replaced export const SCAN_ROOT = 'skills'.

The gate's own run at this head prints the surface, which is where the 18/16/20 arithmetic resolves:

✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
skills/ — 27/27 resolve across 16 file(s)
.claude/skills/ — 60/61 resolve across 4 file(s)

16 under skills/ (was 18 before the guide move) plus 4 under .claude/skills/ = the 20 the card names.

The three claims kept, each verified on main at 6aeba67

1. The scan surface is entirely markdown.scripts/check-skills-paths.mjs:202 — the walker admits nothing else:

elseif(entry.name.endsWith('.md'))files.push(full);

Worth stating precisely, because it is a claim about what the gate reads, not about what the directories hold: the roots also contain 11 non-markdown files (skills/objectui/evals/*.json), which the gate never opens. The comment's wording ("scan surface") is already the accurate one, so it is kept as-is.

2. ci.yml and lint.yml both list '**/*.md' under the paths-ignore of their push trigger.ci.yml:6-11:

push:
branches: [main, develop]paths-ignore:
- '**/*.md'
- 'content/**'
- 'docs/**'
- 'apps/site/**'
- '.changeset/**'

lint.yml:32-36 carries the same list minus apps/site/**. The header's scoping to the push trigger is exact and still required: both files removed paths-ignore from their pull_request trigger (ci.yml:12, lint.yml:37 — "No paths-ignore here any more (objectui#3523, step 2)") and moved that decision into the jobs. Unchanged.

3. This workflow carries no paths or paths-ignore of its own. After comment-stripping there is no such key at all — the YAML parse below enumerates the whole on: block, and scripts/__tests__/check-skills-paths.test.ts:552-555 pins it in both directions.

That pin is also why a comment edit cannot move anything in it. scripts/__tests__/check-skills-paths.test.ts:517-526, the yamlOf helper, drops whole-line comments before every assertion:

.filter((line)=>!/^\s*#/.test(line))

Its own doc comment gives the reason: "this file's own header discusses paths and paths-ignore in prose ... A scan that counted comments would report filters and duplicate homes that no file has."

Why the count is dropped, not restated

lint.yml:26-28 records the same lesson from the other side, about a number that went stale on its own clock: "The order of magnitude is the whole argument; the integer never was." Restating 20 would re-arm the same drift on the next guide added.

Gates — all at head 54d5db8, exit code captured by redirect before any pipe

GateExitIts own verdict line
vitest run scripts/__tests__/check-skills-paths.test.ts (+ 2 derived, below)0Test Files 3 passed (3) · Tests 88 passed (88); lock: VERDICT command-exit 0
node scripts/check-skills-paths.mjs0✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
node scripts/check-doc-links.mjs0Links are valid across 17 scan roots.
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 6108 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs0✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test .github/workflows/skills-paths.yml0✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.
YAML parse, yaml@2.9.0 from node_modules0VERDICT: PARSED DOCUMENT IDENTICAL before/after — comment-only change confirmed

The changeset gate's verdict decides, and it says none is owed: one file changed, zero of them published source.

The YAML check parses both revisions with the repo's own yaml@2.9.0 and compares the parsed documents, so "comment-only" is measured rather than asserted. It also re-prints on: (pull_request, push, merge_group, workflow_dispatch — no paths key), permissions: contents: read, and the three steps unchanged.

Two suites beyond the dispatched list, derived from the actual diff — both assert on this file, so a header edit is in their blast radius:

  • scripts/__tests__/check-pre-install-import-graph.test.ts:262 pins 'skills-paths.yml : skills-paths -> scripts/check-skills-paths.mjs'
  • scripts/__tests__/merge-queue-reporting.test.ts:91 pins skills-paths.yml among the workflows that must subscribe merge_group

Both are in the 3-file, 88-test run above.

Premise notes

Everything in the card held. Three deviations from the dispatch brief, none of them blocking:

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…e count
The header stated the gate's scan surface as `skills/**` over 18 markdown
files. Both halves were stale, and they drifted in two separate steps: the
guide move took the count under `skills/` from 18 to 16, and widening
`SCAN_ROOTS` to `['skills', '.claude/skills']` made the surface 20 files
across two roots.
A reader who took the parenthetical literally would conclude `.claude/skills`
is ungated — the exact failure mode the widening exists to remove. The
enumeration now names both roots and points at `SCAN_ROOTS` as the authority;
the count is dropped rather than restated, because it goes stale on every
guide added. `lint.yml`'s header records the same lesson about a number that
went stale on its own clock.
Comment-only: no change to triggers, jobs, steps, permissions or path
filters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-litantClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (skills lane seat, session session_01LraLgQVGq8egUwfYZpbYt1): in-seat ACCEPT on #7404 (comment 5516027756); NOT GOVERNED per check-governed-queue-guard --test; every check on 54d5db8 green by 20:47Z. Flipped ready and enqueued with auto-merge (squash) per landing-operations B. At MERGED the seat strips pm:dispatched on #7404.


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 5e9b189Sep 2, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-7404-skills-paths-workflow-header branch September 2, 2026 21:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(ci): skills-paths.yml's header states the gate's scan surface as skills/** over 18 files — both halves are stale

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(ci): skills-paths header names both scan roots and drops the file count - #7412

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header
Sep 2, 2026
Merged

docs(ci): skills-paths header names both scan roots and drops the file count#7412
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7404

The header comment of .github/workflows/skills-paths.yml stated the gate's scan surface as skills/** over 18 markdown files. Both halves were stale, and they drifted in two separate steps. Prose only: the parsed workflow document is byte-identical before and after (proof in the gate table).

A reader who took the old parenthetical literally would conclude .claude/skills is ungated — the exact failure mode the widening exists to remove.

Before / after

 # Why this is its own workflow instead of a step in `ci.yml` or `lint.yml`: this
-# gate's ENTIRE scan surface is markdown (`skills/**`, guide prose), and both of-# those workflows list `'**/*.md'`, `content/**` and `docs/**` under the+# gate's ENTIRE scan surface is markdown (`skills/**` and `.claude/skills/**`,+# guide prose — the roots are `SCAN_ROOTS` in the script), and both of those+# workflows list `'**/*.md'`, `content/**` and `docs/**` under the
# `paths-ignore` of their `push` trigger, with no per-job path filter available in
-# It needs no install and no build — a checkout plus one `node` call over 18-# markdown files, a couple of seconds — so keep it that way if you add checks to-# it.+# It needs no install and no build — a checkout plus one `node` call over the+# markdown under those roots, a couple of seconds — so keep it that way if you+# add checks to it. No file count is stated on purpose: it goes stale on every+# guide added, which is how the previous one drifted.

The enumeration now names both roots and points at the declaration rather than copying a list; the count is dropped rather than restated at 20.

The roots, as the script declares them

scripts/check-skills-paths.mjs:154, verbatim:

exportconstSCAN_ROOTS=['skills','.claude/skills'];

Landed in 7c77298"chore(scripts): check-skills-paths scans .claude/skills too — 27 to 88 stated paths under the gate, the #7251 pins restored (#7358) (#7407)". It replaced export const SCAN_ROOT = 'skills'.

The gate's own run at this head prints the surface, which is where the 18/16/20 arithmetic resolves:

✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
skills/ — 27/27 resolve across 16 file(s)
.claude/skills/ — 60/61 resolve across 4 file(s)

16 under skills/ (was 18 before the guide move) plus 4 under .claude/skills/ = the 20 the card names.

The three claims kept, each verified on main at 6aeba67

1. The scan surface is entirely markdown.scripts/check-skills-paths.mjs:202 — the walker admits nothing else:

elseif(entry.name.endsWith('.md'))files.push(full);

Worth stating precisely, because it is a claim about what the gate reads, not about what the directories hold: the roots also contain 11 non-markdown files (skills/objectui/evals/*.json), which the gate never opens. The comment's wording ("scan surface") is already the accurate one, so it is kept as-is.

2. ci.yml and lint.yml both list '**/*.md' under the paths-ignore of their push trigger.ci.yml:6-11:

push:
branches: [main, develop]paths-ignore:
- '**/*.md'
- 'content/**'
- 'docs/**'
- 'apps/site/**'
- '.changeset/**'

lint.yml:32-36 carries the same list minus apps/site/**. The header's scoping to the push trigger is exact and still required: both files removed paths-ignore from their pull_request trigger (ci.yml:12, lint.yml:37 — "No paths-ignore here any more (objectui#3523, step 2)") and moved that decision into the jobs. Unchanged.

3. This workflow carries no paths or paths-ignore of its own. After comment-stripping there is no such key at all — the YAML parse below enumerates the whole on: block, and scripts/__tests__/check-skills-paths.test.ts:552-555 pins it in both directions.

That pin is also why a comment edit cannot move anything in it. scripts/__tests__/check-skills-paths.test.ts:517-526, the yamlOf helper, drops whole-line comments before every assertion:

.filter((line)=>!/^\s*#/.test(line))

Its own doc comment gives the reason: "this file's own header discusses paths and paths-ignore in prose ... A scan that counted comments would report filters and duplicate homes that no file has."

Why the count is dropped, not restated

lint.yml:26-28 records the same lesson from the other side, about a number that went stale on its own clock: "The order of magnitude is the whole argument; the integer never was." Restating 20 would re-arm the same drift on the next guide added.

Gates — all at head 54d5db8, exit code captured by redirect before any pipe

GateExitIts own verdict line
vitest run scripts/__tests__/check-skills-paths.test.ts (+ 2 derived, below)0Test Files 3 passed (3) · Tests 88 passed (88); lock: VERDICT command-exit 0
node scripts/check-skills-paths.mjs0✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
node scripts/check-doc-links.mjs0Links are valid across 17 scan roots.
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 6108 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs0✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test .github/workflows/skills-paths.yml0✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.
YAML parse, yaml@2.9.0 from node_modules0VERDICT: PARSED DOCUMENT IDENTICAL before/after — comment-only change confirmed

The changeset gate's verdict decides, and it says none is owed: one file changed, zero of them published source.

The YAML check parses both revisions with the repo's own yaml@2.9.0 and compares the parsed documents, so "comment-only" is measured rather than asserted. It also re-prints on: (pull_request, push, merge_group, workflow_dispatch — no paths key), permissions: contents: read, and the three steps unchanged.

Two suites beyond the dispatched list, derived from the actual diff — both assert on this file, so a header edit is in their blast radius:

  • scripts/__tests__/check-pre-install-import-graph.test.ts:262 pins 'skills-paths.yml : skills-paths -> scripts/check-skills-paths.mjs'
  • scripts/__tests__/merge-queue-reporting.test.ts:91 pins skills-paths.yml among the workflows that must subscribe merge_group

Both are in the 3-file, 88-test run above.

Premise notes

Everything in the card held. Three deviations from the dispatch brief, none of them blocking:

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…e count
The header stated the gate's scan surface as `skills/**` over 18 markdown
files. Both halves were stale, and they drifted in two separate steps: the
guide move took the count under `skills/` from 18 to 16, and widening
`SCAN_ROOTS` to `['skills', '.claude/skills']` made the surface 20 files
across two roots.
A reader who took the parenthetical literally would conclude `.claude/skills`
is ungated — the exact failure mode the widening exists to remove. The
enumeration now names both roots and points at `SCAN_ROOTS` as the authority;
the count is dropped rather than restated, because it goes stale on every
guide added. `lint.yml`'s header records the same lesson about a number that
went stale on its own clock.
Comment-only: no change to triggers, jobs, steps, permissions or path
filters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-litantClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (skills lane seat, session session_01LraLgQVGq8egUwfYZpbYt1): in-seat ACCEPT on #7404 (comment 5516027756); NOT GOVERNED per check-governed-queue-guard --test; every check on 54d5db8 green by 20:47Z. Flipped ready and enqueued with auto-merge (squash) per landing-operations B. At MERGED the seat strips pm:dispatched on #7404.


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 5e9b189Sep 2, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-7404-skills-paths-workflow-header branch September 2, 2026 21:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(ci): skills-paths.yml's header states the gate's scan surface as skills/** over 18 files — both halves are stale

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(ci): skills-paths header names both scan roots and drops the file count - #7412

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header
Sep 2, 2026
Merged

docs(ci): skills-paths header names both scan roots and drops the file count#7412
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7404

The header comment of .github/workflows/skills-paths.yml stated the gate's scan surface as skills/** over 18 markdown files. Both halves were stale, and they drifted in two separate steps. Prose only: the parsed workflow document is byte-identical before and after (proof in the gate table).

A reader who took the old parenthetical literally would conclude .claude/skills is ungated — the exact failure mode the widening exists to remove.

Before / after

 # Why this is its own workflow instead of a step in `ci.yml` or `lint.yml`: this
-# gate's ENTIRE scan surface is markdown (`skills/**`, guide prose), and both of-# those workflows list `'**/*.md'`, `content/**` and `docs/**` under the+# gate's ENTIRE scan surface is markdown (`skills/**` and `.claude/skills/**`,+# guide prose — the roots are `SCAN_ROOTS` in the script), and both of those+# workflows list `'**/*.md'`, `content/**` and `docs/**` under the
# `paths-ignore` of their `push` trigger, with no per-job path filter available in
-# It needs no install and no build — a checkout plus one `node` call over 18-# markdown files, a couple of seconds — so keep it that way if you add checks to-# it.+# It needs no install and no build — a checkout plus one `node` call over the+# markdown under those roots, a couple of seconds — so keep it that way if you+# add checks to it. No file count is stated on purpose: it goes stale on every+# guide added, which is how the previous one drifted.

The enumeration now names both roots and points at the declaration rather than copying a list; the count is dropped rather than restated at 20.

The roots, as the script declares them

scripts/check-skills-paths.mjs:154, verbatim:

exportconstSCAN_ROOTS=['skills','.claude/skills'];

Landed in 7c77298"chore(scripts): check-skills-paths scans .claude/skills too — 27 to 88 stated paths under the gate, the #7251 pins restored (#7358) (#7407)". It replaced export const SCAN_ROOT = 'skills'.

The gate's own run at this head prints the surface, which is where the 18/16/20 arithmetic resolves:

✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
skills/ — 27/27 resolve across 16 file(s)
.claude/skills/ — 60/61 resolve across 4 file(s)

16 under skills/ (was 18 before the guide move) plus 4 under .claude/skills/ = the 20 the card names.

The three claims kept, each verified on main at 6aeba67

1. The scan surface is entirely markdown.scripts/check-skills-paths.mjs:202 — the walker admits nothing else:

elseif(entry.name.endsWith('.md'))files.push(full);

Worth stating precisely, because it is a claim about what the gate reads, not about what the directories hold: the roots also contain 11 non-markdown files (skills/objectui/evals/*.json), which the gate never opens. The comment's wording ("scan surface") is already the accurate one, so it is kept as-is.

2. ci.yml and lint.yml both list '**/*.md' under the paths-ignore of their push trigger.ci.yml:6-11:

push:
branches: [main, develop]paths-ignore:
- '**/*.md'
- 'content/**'
- 'docs/**'
- 'apps/site/**'
- '.changeset/**'

lint.yml:32-36 carries the same list minus apps/site/**. The header's scoping to the push trigger is exact and still required: both files removed paths-ignore from their pull_request trigger (ci.yml:12, lint.yml:37 — "No paths-ignore here any more (objectui#3523, step 2)") and moved that decision into the jobs. Unchanged.

3. This workflow carries no paths or paths-ignore of its own. After comment-stripping there is no such key at all — the YAML parse below enumerates the whole on: block, and scripts/__tests__/check-skills-paths.test.ts:552-555 pins it in both directions.

That pin is also why a comment edit cannot move anything in it. scripts/__tests__/check-skills-paths.test.ts:517-526, the yamlOf helper, drops whole-line comments before every assertion:

.filter((line)=>!/^\s*#/.test(line))

Its own doc comment gives the reason: "this file's own header discusses paths and paths-ignore in prose ... A scan that counted comments would report filters and duplicate homes that no file has."

Why the count is dropped, not restated

lint.yml:26-28 records the same lesson from the other side, about a number that went stale on its own clock: "The order of magnitude is the whole argument; the integer never was." Restating 20 would re-arm the same drift on the next guide added.

Gates — all at head 54d5db8, exit code captured by redirect before any pipe

GateExitIts own verdict line
vitest run scripts/__tests__/check-skills-paths.test.ts (+ 2 derived, below)0Test Files 3 passed (3) · Tests 88 passed (88); lock: VERDICT command-exit 0
node scripts/check-skills-paths.mjs0✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
node scripts/check-doc-links.mjs0Links are valid across 17 scan roots.
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 6108 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs0✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test .github/workflows/skills-paths.yml0✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.
YAML parse, yaml@2.9.0 from node_modules0VERDICT: PARSED DOCUMENT IDENTICAL before/after — comment-only change confirmed

The changeset gate's verdict decides, and it says none is owed: one file changed, zero of them published source.

The YAML check parses both revisions with the repo's own yaml@2.9.0 and compares the parsed documents, so "comment-only" is measured rather than asserted. It also re-prints on: (pull_request, push, merge_group, workflow_dispatch — no paths key), permissions: contents: read, and the three steps unchanged.

Two suites beyond the dispatched list, derived from the actual diff — both assert on this file, so a header edit is in their blast radius:

  • scripts/__tests__/check-pre-install-import-graph.test.ts:262 pins 'skills-paths.yml : skills-paths -> scripts/check-skills-paths.mjs'
  • scripts/__tests__/merge-queue-reporting.test.ts:91 pins skills-paths.yml among the workflows that must subscribe merge_group

Both are in the 3-file, 88-test run above.

Premise notes

Everything in the card held. Three deviations from the dispatch brief, none of them blocking:

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…e count
The header stated the gate's scan surface as `skills/**` over 18 markdown
files. Both halves were stale, and they drifted in two separate steps: the
guide move took the count under `skills/` from 18 to 16, and widening
`SCAN_ROOTS` to `['skills', '.claude/skills']` made the surface 20 files
across two roots.
A reader who took the parenthetical literally would conclude `.claude/skills`
is ungated — the exact failure mode the widening exists to remove. The
enumeration now names both roots and points at `SCAN_ROOTS` as the authority;
the count is dropped rather than restated, because it goes stale on every
guide added. `lint.yml`'s header records the same lesson about a number that
went stale on its own clock.
Comment-only: no change to triggers, jobs, steps, permissions or path
filters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-litantClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (skills lane seat, session session_01LraLgQVGq8egUwfYZpbYt1): in-seat ACCEPT on #7404 (comment 5516027756); NOT GOVERNED per check-governed-queue-guard --test; every check on 54d5db8 green by 20:47Z. Flipped ready and enqueued with auto-merge (squash) per landing-operations B. At MERGED the seat strips pm:dispatched on #7404.


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 5e9b189Sep 2, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-7404-skills-paths-workflow-header branch September 2, 2026 21:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(ci): skills-paths.yml's header states the gate's scan surface as skills/** over 18 files — both halves are stale

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(ci): skills-paths header names both scan roots and drops the file count - #7412

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header
Sep 2, 2026
Merged

docs(ci): skills-paths header names both scan roots and drops the file count#7412
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7404

The header comment of .github/workflows/skills-paths.yml stated the gate's scan surface as skills/** over 18 markdown files. Both halves were stale, and they drifted in two separate steps. Prose only: the parsed workflow document is byte-identical before and after (proof in the gate table).

A reader who took the old parenthetical literally would conclude .claude/skills is ungated — the exact failure mode the widening exists to remove.

Before / after

 # Why this is its own workflow instead of a step in `ci.yml` or `lint.yml`: this
-# gate's ENTIRE scan surface is markdown (`skills/**`, guide prose), and both of-# those workflows list `'**/*.md'`, `content/**` and `docs/**` under the+# gate's ENTIRE scan surface is markdown (`skills/**` and `.claude/skills/**`,+# guide prose — the roots are `SCAN_ROOTS` in the script), and both of those+# workflows list `'**/*.md'`, `content/**` and `docs/**` under the
# `paths-ignore` of their `push` trigger, with no per-job path filter available in
-# It needs no install and no build — a checkout plus one `node` call over 18-# markdown files, a couple of seconds — so keep it that way if you add checks to-# it.+# It needs no install and no build — a checkout plus one `node` call over the+# markdown under those roots, a couple of seconds — so keep it that way if you+# add checks to it. No file count is stated on purpose: it goes stale on every+# guide added, which is how the previous one drifted.

The enumeration now names both roots and points at the declaration rather than copying a list; the count is dropped rather than restated at 20.

The roots, as the script declares them

scripts/check-skills-paths.mjs:154, verbatim:

exportconstSCAN_ROOTS=['skills','.claude/skills'];

Landed in 7c77298"chore(scripts): check-skills-paths scans .claude/skills too — 27 to 88 stated paths under the gate, the #7251 pins restored (#7358) (#7407)". It replaced export const SCAN_ROOT = 'skills'.

The gate's own run at this head prints the surface, which is where the 18/16/20 arithmetic resolves:

✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
skills/ — 27/27 resolve across 16 file(s)
.claude/skills/ — 60/61 resolve across 4 file(s)

16 under skills/ (was 18 before the guide move) plus 4 under .claude/skills/ = the 20 the card names.

The three claims kept, each verified on main at 6aeba67

1. The scan surface is entirely markdown.scripts/check-skills-paths.mjs:202 — the walker admits nothing else:

elseif(entry.name.endsWith('.md'))files.push(full);

Worth stating precisely, because it is a claim about what the gate reads, not about what the directories hold: the roots also contain 11 non-markdown files (skills/objectui/evals/*.json), which the gate never opens. The comment's wording ("scan surface") is already the accurate one, so it is kept as-is.

2. ci.yml and lint.yml both list '**/*.md' under the paths-ignore of their push trigger.ci.yml:6-11:

push:
branches: [main, develop]paths-ignore:
- '**/*.md'
- 'content/**'
- 'docs/**'
- 'apps/site/**'
- '.changeset/**'

lint.yml:32-36 carries the same list minus apps/site/**. The header's scoping to the push trigger is exact and still required: both files removed paths-ignore from their pull_request trigger (ci.yml:12, lint.yml:37 — "No paths-ignore here any more (objectui#3523, step 2)") and moved that decision into the jobs. Unchanged.

3. This workflow carries no paths or paths-ignore of its own. After comment-stripping there is no such key at all — the YAML parse below enumerates the whole on: block, and scripts/__tests__/check-skills-paths.test.ts:552-555 pins it in both directions.

That pin is also why a comment edit cannot move anything in it. scripts/__tests__/check-skills-paths.test.ts:517-526, the yamlOf helper, drops whole-line comments before every assertion:

.filter((line)=>!/^\s*#/.test(line))

Its own doc comment gives the reason: "this file's own header discusses paths and paths-ignore in prose ... A scan that counted comments would report filters and duplicate homes that no file has."

Why the count is dropped, not restated

lint.yml:26-28 records the same lesson from the other side, about a number that went stale on its own clock: "The order of magnitude is the whole argument; the integer never was." Restating 20 would re-arm the same drift on the next guide added.

Gates — all at head 54d5db8, exit code captured by redirect before any pipe

GateExitIts own verdict line
vitest run scripts/__tests__/check-skills-paths.test.ts (+ 2 derived, below)0Test Files 3 passed (3) · Tests 88 passed (88); lock: VERDICT command-exit 0
node scripts/check-skills-paths.mjs0✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
node scripts/check-doc-links.mjs0Links are valid across 17 scan roots.
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 6108 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs0✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test .github/workflows/skills-paths.yml0✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.
YAML parse, yaml@2.9.0 from node_modules0VERDICT: PARSED DOCUMENT IDENTICAL before/after — comment-only change confirmed

The changeset gate's verdict decides, and it says none is owed: one file changed, zero of them published source.

The YAML check parses both revisions with the repo's own yaml@2.9.0 and compares the parsed documents, so "comment-only" is measured rather than asserted. It also re-prints on: (pull_request, push, merge_group, workflow_dispatch — no paths key), permissions: contents: read, and the three steps unchanged.

Two suites beyond the dispatched list, derived from the actual diff — both assert on this file, so a header edit is in their blast radius:

  • scripts/__tests__/check-pre-install-import-graph.test.ts:262 pins 'skills-paths.yml : skills-paths -> scripts/check-skills-paths.mjs'
  • scripts/__tests__/merge-queue-reporting.test.ts:91 pins skills-paths.yml among the workflows that must subscribe merge_group

Both are in the 3-file, 88-test run above.

Premise notes

Everything in the card held. Three deviations from the dispatch brief, none of them blocking:

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…e count
The header stated the gate's scan surface as `skills/**` over 18 markdown
files. Both halves were stale, and they drifted in two separate steps: the
guide move took the count under `skills/` from 18 to 16, and widening
`SCAN_ROOTS` to `['skills', '.claude/skills']` made the surface 20 files
across two roots.
A reader who took the parenthetical literally would conclude `.claude/skills`
is ungated — the exact failure mode the widening exists to remove. The
enumeration now names both roots and points at `SCAN_ROOTS` as the authority;
the count is dropped rather than restated, because it goes stale on every
guide added. `lint.yml`'s header records the same lesson about a number that
went stale on its own clock.
Comment-only: no change to triggers, jobs, steps, permissions or path
filters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-litantClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (skills lane seat, session session_01LraLgQVGq8egUwfYZpbYt1): in-seat ACCEPT on #7404 (comment 5516027756); NOT GOVERNED per check-governed-queue-guard --test; every check on 54d5db8 green by 20:47Z. Flipped ready and enqueued with auto-merge (squash) per landing-operations B. At MERGED the seat strips pm:dispatched on #7404.


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 5e9b189Sep 2, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-7404-skills-paths-workflow-header branch September 2, 2026 21:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(ci): skills-paths.yml's header states the gate's scan surface as skills/** over 18 files — both halves are stale

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(ci): skills-paths header names both scan roots and drops the file count - #7412

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header
Sep 2, 2026
Merged

docs(ci): skills-paths header names both scan roots and drops the file count#7412
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7404

The header comment of .github/workflows/skills-paths.yml stated the gate's scan surface as skills/** over 18 markdown files. Both halves were stale, and they drifted in two separate steps. Prose only: the parsed workflow document is byte-identical before and after (proof in the gate table).

A reader who took the old parenthetical literally would conclude .claude/skills is ungated — the exact failure mode the widening exists to remove.

Before / after

 # Why this is its own workflow instead of a step in `ci.yml` or `lint.yml`: this
-# gate's ENTIRE scan surface is markdown (`skills/**`, guide prose), and both of-# those workflows list `'**/*.md'`, `content/**` and `docs/**` under the+# gate's ENTIRE scan surface is markdown (`skills/**` and `.claude/skills/**`,+# guide prose — the roots are `SCAN_ROOTS` in the script), and both of those+# workflows list `'**/*.md'`, `content/**` and `docs/**` under the
# `paths-ignore` of their `push` trigger, with no per-job path filter available in
-# It needs no install and no build — a checkout plus one `node` call over 18-# markdown files, a couple of seconds — so keep it that way if you add checks to-# it.+# It needs no install and no build — a checkout plus one `node` call over the+# markdown under those roots, a couple of seconds — so keep it that way if you+# add checks to it. No file count is stated on purpose: it goes stale on every+# guide added, which is how the previous one drifted.

The enumeration now names both roots and points at the declaration rather than copying a list; the count is dropped rather than restated at 20.

The roots, as the script declares them

scripts/check-skills-paths.mjs:154, verbatim:

exportconstSCAN_ROOTS=['skills','.claude/skills'];

Landed in 7c77298"chore(scripts): check-skills-paths scans .claude/skills too — 27 to 88 stated paths under the gate, the #7251 pins restored (#7358) (#7407)". It replaced export const SCAN_ROOT = 'skills'.

The gate's own run at this head prints the surface, which is where the 18/16/20 arithmetic resolves:

✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
skills/ — 27/27 resolve across 16 file(s)
.claude/skills/ — 60/61 resolve across 4 file(s)

16 under skills/ (was 18 before the guide move) plus 4 under .claude/skills/ = the 20 the card names.

The three claims kept, each verified on main at 6aeba67

1. The scan surface is entirely markdown.scripts/check-skills-paths.mjs:202 — the walker admits nothing else:

elseif(entry.name.endsWith('.md'))files.push(full);

Worth stating precisely, because it is a claim about what the gate reads, not about what the directories hold: the roots also contain 11 non-markdown files (skills/objectui/evals/*.json), which the gate never opens. The comment's wording ("scan surface") is already the accurate one, so it is kept as-is.

2. ci.yml and lint.yml both list '**/*.md' under the paths-ignore of their push trigger.ci.yml:6-11:

push:
branches: [main, develop]paths-ignore:
- '**/*.md'
- 'content/**'
- 'docs/**'
- 'apps/site/**'
- '.changeset/**'

lint.yml:32-36 carries the same list minus apps/site/**. The header's scoping to the push trigger is exact and still required: both files removed paths-ignore from their pull_request trigger (ci.yml:12, lint.yml:37 — "No paths-ignore here any more (objectui#3523, step 2)") and moved that decision into the jobs. Unchanged.

3. This workflow carries no paths or paths-ignore of its own. After comment-stripping there is no such key at all — the YAML parse below enumerates the whole on: block, and scripts/__tests__/check-skills-paths.test.ts:552-555 pins it in both directions.

That pin is also why a comment edit cannot move anything in it. scripts/__tests__/check-skills-paths.test.ts:517-526, the yamlOf helper, drops whole-line comments before every assertion:

.filter((line)=>!/^\s*#/.test(line))

Its own doc comment gives the reason: "this file's own header discusses paths and paths-ignore in prose ... A scan that counted comments would report filters and duplicate homes that no file has."

Why the count is dropped, not restated

lint.yml:26-28 records the same lesson from the other side, about a number that went stale on its own clock: "The order of magnitude is the whole argument; the integer never was." Restating 20 would re-arm the same drift on the next guide added.

Gates — all at head 54d5db8, exit code captured by redirect before any pipe

GateExitIts own verdict line
vitest run scripts/__tests__/check-skills-paths.test.ts (+ 2 derived, below)0Test Files 3 passed (3) · Tests 88 passed (88); lock: VERDICT command-exit 0
node scripts/check-skills-paths.mjs0✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
node scripts/check-doc-links.mjs0Links are valid across 17 scan roots.
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 6108 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs0✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test .github/workflows/skills-paths.yml0✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.
YAML parse, yaml@2.9.0 from node_modules0VERDICT: PARSED DOCUMENT IDENTICAL before/after — comment-only change confirmed

The changeset gate's verdict decides, and it says none is owed: one file changed, zero of them published source.

The YAML check parses both revisions with the repo's own yaml@2.9.0 and compares the parsed documents, so "comment-only" is measured rather than asserted. It also re-prints on: (pull_request, push, merge_group, workflow_dispatch — no paths key), permissions: contents: read, and the three steps unchanged.

Two suites beyond the dispatched list, derived from the actual diff — both assert on this file, so a header edit is in their blast radius:

  • scripts/__tests__/check-pre-install-import-graph.test.ts:262 pins 'skills-paths.yml : skills-paths -> scripts/check-skills-paths.mjs'
  • scripts/__tests__/merge-queue-reporting.test.ts:91 pins skills-paths.yml among the workflows that must subscribe merge_group

Both are in the 3-file, 88-test run above.

Premise notes

Everything in the card held. Three deviations from the dispatch brief, none of them blocking:

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…e count
The header stated the gate's scan surface as `skills/**` over 18 markdown
files. Both halves were stale, and they drifted in two separate steps: the
guide move took the count under `skills/` from 18 to 16, and widening
`SCAN_ROOTS` to `['skills', '.claude/skills']` made the surface 20 files
across two roots.
A reader who took the parenthetical literally would conclude `.claude/skills`
is ungated — the exact failure mode the widening exists to remove. The
enumeration now names both roots and points at `SCAN_ROOTS` as the authority;
the count is dropped rather than restated, because it goes stale on every
guide added. `lint.yml`'s header records the same lesson about a number that
went stale on its own clock.
Comment-only: no change to triggers, jobs, steps, permissions or path
filters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-litantClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (skills lane seat, session session_01LraLgQVGq8egUwfYZpbYt1): in-seat ACCEPT on #7404 (comment 5516027756); NOT GOVERNED per check-governed-queue-guard --test; every check on 54d5db8 green by 20:47Z. Flipped ready and enqueued with auto-merge (squash) per landing-operations B. At MERGED the seat strips pm:dispatched on #7404.


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 5e9b189Sep 2, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-7404-skills-paths-workflow-header branch September 2, 2026 21:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(ci): skills-paths.yml's header states the gate's scan surface as skills/** over 18 files — both halves are stale

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(ci): skills-paths header names both scan roots and drops the file count - #7412

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header
Sep 2, 2026
Merged

docs(ci): skills-paths header names both scan roots and drops the file count#7412
os-litant merged 1 commit into
mainfrom
claude/issue-7404-skills-paths-workflow-header

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#7404

The header comment of .github/workflows/skills-paths.yml stated the gate's scan surface as skills/** over 18 markdown files. Both halves were stale, and they drifted in two separate steps. Prose only: the parsed workflow document is byte-identical before and after (proof in the gate table).

A reader who took the old parenthetical literally would conclude .claude/skills is ungated — the exact failure mode the widening exists to remove.

Before / after

 # Why this is its own workflow instead of a step in `ci.yml` or `lint.yml`: this
-# gate's ENTIRE scan surface is markdown (`skills/**`, guide prose), and both of-# those workflows list `'**/*.md'`, `content/**` and `docs/**` under the+# gate's ENTIRE scan surface is markdown (`skills/**` and `.claude/skills/**`,+# guide prose — the roots are `SCAN_ROOTS` in the script), and both of those+# workflows list `'**/*.md'`, `content/**` and `docs/**` under the
# `paths-ignore` of their `push` trigger, with no per-job path filter available in
-# It needs no install and no build — a checkout plus one `node` call over 18-# markdown files, a couple of seconds — so keep it that way if you add checks to-# it.+# It needs no install and no build — a checkout plus one `node` call over the+# markdown under those roots, a couple of seconds — so keep it that way if you+# add checks to it. No file count is stated on purpose: it goes stale on every+# guide added, which is how the previous one drifted.

The enumeration now names both roots and points at the declaration rather than copying a list; the count is dropped rather than restated at 20.

The roots, as the script declares them

scripts/check-skills-paths.mjs:154, verbatim:

exportconstSCAN_ROOTS=['skills','.claude/skills'];

Landed in 7c77298"chore(scripts): check-skills-paths scans .claude/skills too — 27 to 88 stated paths under the gate, the #7251 pins restored (#7358) (#7407)". It replaced export const SCAN_ROOT = 'skills'.

The gate's own run at this head prints the surface, which is where the 18/16/20 arithmetic resolves:

✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
skills/ — 27/27 resolve across 16 file(s)
.claude/skills/ — 60/61 resolve across 4 file(s)

16 under skills/ (was 18 before the guide move) plus 4 under .claude/skills/ = the 20 the card names.

The three claims kept, each verified on main at 6aeba67

1. The scan surface is entirely markdown.scripts/check-skills-paths.mjs:202 — the walker admits nothing else:

elseif(entry.name.endsWith('.md'))files.push(full);

Worth stating precisely, because it is a claim about what the gate reads, not about what the directories hold: the roots also contain 11 non-markdown files (skills/objectui/evals/*.json), which the gate never opens. The comment's wording ("scan surface") is already the accurate one, so it is kept as-is.

2. ci.yml and lint.yml both list '**/*.md' under the paths-ignore of their push trigger.ci.yml:6-11:

push:
branches: [main, develop]paths-ignore:
- '**/*.md'
- 'content/**'
- 'docs/**'
- 'apps/site/**'
- '.changeset/**'

lint.yml:32-36 carries the same list minus apps/site/**. The header's scoping to the push trigger is exact and still required: both files removed paths-ignore from their pull_request trigger (ci.yml:12, lint.yml:37 — "No paths-ignore here any more (objectui#3523, step 2)") and moved that decision into the jobs. Unchanged.

3. This workflow carries no paths or paths-ignore of its own. After comment-stripping there is no such key at all — the YAML parse below enumerates the whole on: block, and scripts/__tests__/check-skills-paths.test.ts:552-555 pins it in both directions.

That pin is also why a comment edit cannot move anything in it. scripts/__tests__/check-skills-paths.test.ts:517-526, the yamlOf helper, drops whole-line comments before every assertion:

.filter((line)=>!/^\s*#/.test(line))

Its own doc comment gives the reason: "this file's own header discusses paths and paths-ignore in prose ... A scan that counted comments would report filters and duplicate homes that no file has."

Why the count is dropped, not restated

lint.yml:26-28 records the same lesson from the other side, about a number that went stale on its own clock: "The order of magnitude is the whole argument; the integer never was." Restating 20 would re-arm the same drift on the next guide added.

Gates — all at head 54d5db8, exit code captured by redirect before any pipe

GateExitIts own verdict line
vitest run scripts/__tests__/check-skills-paths.test.ts (+ 2 derived, below)0Test Files 3 passed (3) · Tests 88 passed (88); lock: VERDICT command-exit 0
node scripts/check-skills-paths.mjs0✅ check-skills-paths: OK (87/88 stated path(s) resolve across 20 guide file(s); 1 baselined).
node scripts/check-doc-links.mjs0Links are valid across 17 scan roots.
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 6108 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs0✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test .github/workflows/skills-paths.yml0✅ NOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.
YAML parse, yaml@2.9.0 from node_modules0VERDICT: PARSED DOCUMENT IDENTICAL before/after — comment-only change confirmed

The changeset gate's verdict decides, and it says none is owed: one file changed, zero of them published source.

The YAML check parses both revisions with the repo's own yaml@2.9.0 and compares the parsed documents, so "comment-only" is measured rather than asserted. It also re-prints on: (pull_request, push, merge_group, workflow_dispatch — no paths key), permissions: contents: read, and the three steps unchanged.

Two suites beyond the dispatched list, derived from the actual diff — both assert on this file, so a header edit is in their blast radius:

  • scripts/__tests__/check-pre-install-import-graph.test.ts:262 pins 'skills-paths.yml : skills-paths -> scripts/check-skills-paths.mjs'
  • scripts/__tests__/merge-queue-reporting.test.ts:91 pins skills-paths.yml among the workflows that must subscribe merge_group

Both are in the 3-file, 88-test run above.

Premise notes

Everything in the card held. Three deviations from the dispatch brief, none of them blocking:

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…e count
The header stated the gate's scan surface as `skills/**` over 18 markdown
files. Both halves were stale, and they drifted in two separate steps: the
guide move took the count under `skills/` from 18 to 16, and widening
`SCAN_ROOTS` to `['skills', '.claude/skills']` made the surface 20 files
across two roots.
A reader who took the parenthetical literally would conclude `.claude/skills`
is ungated — the exact failure mode the widening exists to remove. The
enumeration now names both roots and points at `SCAN_ROOTS` as the authority;
the count is dropped rather than restated, because it goes stale on every
guide added. `lint.yml`'s header records the same lesson about a number that
went stale on its own clock.
Comment-only: no change to triggers, jobs, steps, permissions or path
filters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@os-litantClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (skills lane seat, session session_01LraLgQVGq8egUwfYZpbYt1): in-seat ACCEPT on #7404 (comment 5516027756); NOT GOVERNED per check-governed-queue-guard --test; every check on 54d5db8 green by 20:47Z. Flipped ready and enqueued with auto-merge (squash) per landing-operations B. At MERGED the seat strips pm:dispatched on #7404.


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 5e9b189Sep 2, 2026
29 checks passed
@os-litant
os-litant deleted the claude/issue-7404-skills-paths-workflow-header branch September 2, 2026 21:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(ci): skills-paths.yml's header states the gate's scan surface as skills/** over 18 files — both halves are stale

2 participants

@os-litant@claude