chore: regenerate agent-skills index when SKILL.md changes#9
chore: regenerate agent-skills index when SKILL.md changes#9Bohdan-Itrinity wants to merge 1 commit into
Conversation
Add generator script and CI workflow to keep .well-known/agent-skills/index.json in sync with skills/*/SKILL.md descriptions and sha256 digests. Co-authored-by: Cursor <cursoragent@cursor.com>
WalkthroughA new Node.js script ( ChangesAgent Skills Index Generator & CI Workflow
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/agent-skills-index.yml:
- Around line 16-18: The global permissions block grants `contents: write` to
all jobs in the workflow, which violates the principle of least privilege.
Remove the workflow-level permissions block that contains `contents: write`, and
instead add job-specific permissions blocks for each job. For the pull request
validation job, set permissions to `contents: read` to restrict it to read-only
access. For the job that performs auto-commits on the main branch, set
permissions to `contents: write`. This ensures that only jobs that require write
access to the repository contents have that permission granted.
- Around line 25-26: The checkout action at the Checkout step is using the
default behavior which persists credentials in git config for subsequent steps.
To reduce the security risk from a write-capable token, add the
persist-credentials option set to false to the actions/checkout@v4 step. This
should be added in the with section of the checkout action to disable credential
persistence.
- Around line 3-14: The workflow lacks concurrency control which allows multiple
runs to overlap on the same ref, causing race conditions and potential push
failures. Add a concurrency section at the top level of the workflow (at the
same indentation level as the `on:` trigger section) with a group identifier
based on github.ref and set cancel-in-progress to true to automatically cancel
redundant in-flight runs when a new push occurs on the same branch or ref.
- Around line 26-30: The GitHub Actions in the workflow file are pinned to
version tags (`@v4`) instead of full commit SHAs, which leaves the workflow
vulnerable to upstream changes. Replace the version tag pinning in both the
actions/checkout and actions/setup-node steps with their respective full commit
SHAs to ensure immutability and verifiability of the workflow execution.
In `@scripts/generate-agent-skills-index.mjs`:
- Around line 37-57: The parseDescription() function's regex pattern for
matching the description value only captures single-line descriptions and fails
to handle valid YAML multiline syntax like description: | or description: >.
Update the regex that creates descriptionMatch to handle both single-line and
multiline YAML description formats by allowing optional whitespace and
pipe/greater-than operators after the colon, and then implement logic to
properly extract and process the description content whether it's inline or
spans multiple lines in the YAML frontmatter.
- Around line 86-93: The digest is being computed from the decoded UTF-8 text
string rather than from the raw file bytes, which can cause digest mismatches
due to encoding variations. Modify the file reading logic to read the file as
raw bytes first (remove the "utf8" encoding parameter from fs.readFileSync),
compute the digest from those raw bytes using the digestFor function, and then
separately decode the bytes to UTF-8 text for use with the parseDescription
function. This ensures the digest value matches the actual file bytes rather
than a decoded text representation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a49bf05a-7c31-45ca-bf46-69a7386ae20b
📒 Files selected for processing (2)
.github/workflows/agent-skills-index.ymlscripts/generate-agent-skills-index.mjs
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - 'skills/*/SKILL.md' | ||
| - 'scripts/generate-agent-skills-index.mjs' | ||
| pull_request: | ||
| paths: | ||
| - 'skills/*/SKILL.md' | ||
| - 'scripts/generate-agent-skills-index.mjs' | ||
| - '.well-known/agent-skills/index.json' | ||
| workflow_dispatch: |
There was a problem hiding this comment.
Add concurrency control to avoid overlapping runs on the same ref.
Between Line 3 and Line 14, no concurrency group is defined. Multiple close pushes can race, causing redundant regenerate/commit attempts and occasional push failures.
Suggested fix
on:
push:
@@
workflow_dispatch:
+
+concurrency:
+ group: agent-skills-index-${{ github.ref }}
+ cancel-in-progress: true📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'skills/*/SKILL.md' | |
| - 'scripts/generate-agent-skills-index.mjs' | |
| pull_request: | |
| paths: | |
| - 'skills/*/SKILL.md' | |
| - 'scripts/generate-agent-skills-index.mjs' | |
| - '.well-known/agent-skills/index.json' | |
| workflow_dispatch: | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'skills/*/SKILL.md' | |
| - 'scripts/generate-agent-skills-index.mjs' | |
| pull_request: | |
| paths: | |
| - 'skills/*/SKILL.md' | |
| - 'scripts/generate-agent-skills-index.mjs' | |
| - '.well-known/agent-skills/index.json' | |
| workflow_dispatch: | |
| concurrency: | |
| group: agent-skills-index-${{ github.ref }} | |
| cancel-in-progress: true |
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 3-14: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting
(concurrency-limits)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/agent-skills-index.yml around lines 3 - 14, The workflow
lacks concurrency control which allows multiple runs to overlap on the same ref,
causing race conditions and potential push failures. Add a concurrency section
at the top level of the workflow (at the same indentation level as the `on:`
trigger section) with a group identifier based on github.ref and set
cancel-in-progress to true to automatically cancel redundant in-flight runs when
a new push occurs on the same branch or ref.
Source: Linters/SAST tools
| permissions: | ||
| contents: write | ||
|
|
There was a problem hiding this comment.
Permissions are broader than required for PR validation.
At Line 17, contents: write is granted workflow-wide, including pull_request runs that only need read access. Split permissions by job (read for PR check, write only for main auto-commit) to enforce least privilege.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 17-17: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level
(excessive-permissions)
[warning] 17-17: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/agent-skills-index.yml around lines 16 - 18, The global
permissions block grants `contents: write` to all jobs in the workflow, which
violates the principle of least privilege. Remove the workflow-level permissions
block that contains `contents: write`, and instead add job-specific permissions
blocks for each job. For the pull request validation job, set permissions to
`contents: read` to restrict it to read-only access. For the job that performs
auto-commits on the main branch, set permissions to `contents: write`. This
ensures that only jobs that require write access to the repository contents have
that permission granted.
Source: Linters/SAST tools
| - name: Checkout | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
Disable credential persistence on checkout for safer token handling.
At Line 26, checkout defaults to persisting credentials in git config for later steps. With a write-capable token in this job, reducing credential lifetime lowers blast radius if later steps are compromised.
Suggested fix
- name: Checkout
uses: actions/checkout@v4
+ with:
+ persist-credentials: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false |
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 25-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 26-26: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/agent-skills-index.yml around lines 25 - 26, The checkout
action at the Checkout step is using the default behavior which persists
credentials in git config for subsequent steps. To reduce the security risk from
a write-capable token, add the persist-credentials option set to false to the
actions/checkout@v4 step. This should be added in the with section of the
checkout action to disable credential persistence.
Source: Linters/SAST tools
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify all workflow actions are SHA-pinned.
# Expected: each `uses:` should include a full commit SHA (40 hex chars).
rg -nP --type=yaml '^\s*uses:\s*[^@]+@(?!(?:[a-f0-9]{40})\b).+' .github/workflowsRepository: uptimerobot/ai
Length of output: 216
🏁 Script executed:
cat -n .github/workflows/agent-skills-index.yml | head -35Repository: uptimerobot/ai
Length of output: 1014
🏁 Script executed:
rg -nP --type=yaml '^\s*uses:\s*[^@]+@(?!(?:[a-f0-9]{40})\b).+' .github/workflowsRepository: uptimerobot/ai
Length of output: 216
Pin GitHub Actions to commit SHAs instead of version tags.
Lines 26 and 29 use tag-pinning (@v4) rather than commit SHA pinning, which leaves the workflow exposed to upstream changes. Replace with full commit SHAs for immutability and verifiability:
Required changes
- name: Checkout
uses: actions/checkout@<full-commit-sha>
- name: Setup Node.js
uses: actions/setup-node@<full-commit-sha>🧰 Tools
🪛 zizmor (1.25.2)
[error] 26-26: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 29-29: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/agent-skills-index.yml around lines 26 - 30, The GitHub
Actions in the workflow file are pinned to version tags (`@v4`) instead of full
commit SHAs, which leaves the workflow vulnerable to upstream changes. Replace
the version tag pinning in both the actions/checkout and actions/setup-node
steps with their respective full commit SHAs to ensure immutability and
verifiability of the workflow execution.
Source: Linters/SAST tools
| function parseDescription(skillMd) { | ||
| const match = skillMd.match(/^---\r?\n([\s\S]*?)\r?\n---/); | ||
| if (!match) { | ||
| fail("SKILL.md is missing YAML frontmatter"); | ||
| } | ||
|
|
||
| const frontmatter = match[1]; | ||
| const descriptionMatch = frontmatter.match(/^description:\s*(.+)$/m); | ||
| if (!descriptionMatch) { | ||
| fail("SKILL.md frontmatter is missing description"); | ||
| } | ||
|
|
||
| let description = descriptionMatch[1].trim(); | ||
| if ( | ||
| (description.startsWith('"') && description.endsWith('"')) || | ||
| (description.startsWith("'") && description.endsWith("'")) | ||
| ) { | ||
| description = description.slice(1, -1); | ||
| } | ||
|
|
||
| return description; |
There was a problem hiding this comment.
parseDescription() rejects valid YAML descriptions.
At Line 44, the regex only accepts single-line description: values. Valid frontmatter like description: | / > (multiline YAML) will fail or produce incorrect data, making index generation brittle against legitimate SKILL.md metadata.
Suggested fix
function parseDescription(skillMd) {
const match = skillMd.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) {
fail("SKILL.md is missing YAML frontmatter");
}
const frontmatter = match[1];
- const descriptionMatch = frontmatter.match(/^description:\s*(.+)$/m);
+ const descriptionMatch = frontmatter.match(
+ /^description:\s*(.+)$|^description:\s*[>|]\s*\n([\s\S]*?)(?=^\w[\w-]*:\s*|\s*$)/m,
+ );
if (!descriptionMatch) {
fail("SKILL.md frontmatter is missing description");
}
- let description = descriptionMatch[1].trim();
+ let description = (descriptionMatch[1] ?? descriptionMatch[2] ?? "").trim();
if (
(description.startsWith('"') && description.endsWith('"')) ||
(description.startsWith("'") && description.endsWith("'"))
) {
description = description.slice(1, -1);
}
return description;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/generate-agent-skills-index.mjs` around lines 37 - 57, The
parseDescription() function's regex pattern for matching the description value
only captures single-line descriptions and fails to handle valid YAML multiline
syntax like description: | or description: >. Update the regex that creates
descriptionMatch to handle both single-line and multiline YAML description
formats by allowing optional whitespace and pipe/greater-than operators after
the colon, and then implement logic to properly extract and process the
description content whether it's inline or spans multiple lines in the YAML
frontmatter.
| const content = fs.readFileSync(skillPath, "utf8"); | ||
| return { | ||
| name, | ||
| type: "skill-md", | ||
| description: parseDescription(content), | ||
| url: `https://raw.githubusercontent.com/${GITHUB_ORG_REPO}/${branch}/skills/${name}/SKILL.md`, | ||
| digest: digestFor(content), | ||
| }; |
There was a problem hiding this comment.
Digest should be computed from file bytes, not decoded text.
At Line 86 and Line 92, the file is read as UTF-8 text and then hashed. The stated contract is hash of SKILL.md bytes; hashing decoded text can diverge for non-canonical encodings/BOM edge cases and break cross-system digest verification.
Suggested fix
-function digestFor(content) {
- const hash = crypto.createHash("sha256").update(content, "utf8").digest("hex");
+function digestFor(contentBytes) {
+ const hash = crypto.createHash("sha256").update(contentBytes).digest("hex");
return `sha256:${hash}`;
}
@@
- const content = fs.readFileSync(skillPath, "utf8");
+ const contentBytes = fs.readFileSync(skillPath);
+ const content = contentBytes.toString("utf8");
return {
name,
type: "skill-md",
description: parseDescription(content),
url: `https://raw.githubusercontent.com/${GITHUB_ORG_REPO}/${branch}/skills/${name}/SKILL.md`,
- digest: digestFor(content),
+ digest: digestFor(contentBytes),
};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/generate-agent-skills-index.mjs` around lines 86 - 93, The digest is
being computed from the decoded UTF-8 text string rather than from the raw file
bytes, which can cause digest mismatches due to encoding variations. Modify the
file reading logic to read the file as raw bytes first (remove the "utf8"
encoding parameter from fs.readFileSync), compute the digest from those raw
bytes using the digestFor function, and then separately decode the bytes to
UTF-8 text for use with the parseDescription function. This ensures the digest
value matches the actual file bytes rather than a decoded text representation.
Add generator script and CI workflow to keep .well-known/agent-skills/index.json in sync with skills/*/SKILL.md descriptions and sha256 digests.
Summary by CodeRabbit