Add Accepted Entry workflow (jslee02/awesome-robotics-libraries)
The Add Accepted Entry workflow from jslee02/awesome-robotics-libraries, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get caching, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Add Accepted Entry workflow from the jslee02/awesome-robotics-libraries repository, a real project running GitHub Actions. It is shown here with attribution under its CC0-1.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Add Accepted Entry
on:
issues:
types: [labeled]
workflow_dispatch:
inputs:
issue_number:
description: Issue number to process
required: true
type: number
jobs:
add-entry:
if: |
(github.event_name == 'issues' && github.event.label.name == 'accepted') ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- run: pip install pyyaml jsonschema
- name: Parse issue and build entry
id: parse
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/github-script@v9
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issueNumber = context.eventName === 'workflow_dispatch'
? Number('${{ inputs.issue_number }}')
: context.issue.number;
const issueData = context.eventName === 'workflow_dispatch'
? (await github.rest.issues.get({
owner,
repo,
issue_number: issueNumber,
})).data
: context.payload.issue;
const body = issueData.body || '';
function normalizeGitHubRepo(value) {
if (!value) return '';
const trimmed = value.trim();
const slugMatch = trimmed.match(/^(?:https?:\/\/github\.com\/)?([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+?)(?:\.git|\/)?$/);
return slugMatch ? slugMatch[1] : '';
}
function extractField(body, header) {
const re = new RegExp(`### ${header}\\s*\\n\\s*\\n?\\s*([\\s\\S]*?)(?=\\n###|$)`);
const m = body.match(re);
return m ? m[1].trim() : '';
}
const labels = (issueData.labels || []).map(label => typeof label === 'string' ? label : label.name);
if (!labels.includes('accepted')) {
core.setFailed(`Issue #${issueNumber} does not have the accepted label`);
return;
}
const name = extractField(body, 'Resource Name') || extractField(body, 'Library Name');
const url = extractField(body, 'URL');
const category = extractField(body, 'Category');
const description = extractField(body, 'Description');
// GitHub repo: from structured field or from URL
let ghRepo = normalizeGitHubRepo(extractField(body, 'GitHub Repository'));
if (!ghRepo) {
const urlMatch = body.match(/github\.com\/([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+)/);
if (urlMatch) ghRepo = urlMatch[1];
}
if (!name || !category) {
core.setFailed('Could not parse required fields (name, category) from issue body');
return;
}
// Map category to YAML filename
const categoryMap = {
'Dynamics Simulation': 'dynamics-simulation',
'Inverse Kinematics': 'inverse-kinematics',
'Machine Learning': 'machine-learning',
'Motion Planning and Control': 'motion-planning',
'Optimization': 'optimization',
'Robot Modeling': 'robot-modeling',
'Robot Platform': 'robot-platform',
'Reinforcement Learning for Robotics': 'reinforcement-learning',
'SLAM': 'slam',
'Vision': 'vision',
'Fluid': 'fluid',
'Grasping': 'grasping',
'Humanoid Robotics': 'humanoid-robotics',
'Multiphysics': 'multiphysics',
'Math': 'math',
'ETC': 'etc',
'Simulators': 'simulators',
'Other Awesome Lists': 'other-awesome-lists',
};
const yamlFile = categoryMap[category];
if (!yamlFile) {
core.setFailed(`Unknown category: ${category}`);
return;
}
core.setOutput('name', name);
core.setOutput('url', url);
core.setOutput('github_repo', ghRepo);
core.setOutput('category', category);
core.setOutput('description', description);
core.setOutput('yaml_file', yamlFile);
core.setOutput('issue_number', issueNumber);
- name: Fetch metadata and add YAML entry
if: success()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ENTRY_NAME: ${{ steps.parse.outputs.name }}
ENTRY_URL: ${{ steps.parse.outputs.url }}
ENTRY_GITHUB: ${{ steps.parse.outputs.github_repo }}
ENTRY_DESCRIPTION: ${{ steps.parse.outputs.description }}
ENTRY_YAML_FILE: ${{ steps.parse.outputs.yaml_file }}
run: |
python3 - <<'PYTHON_SCRIPT'
import os
import sys
from pathlib import Path
import yaml
from scripts.github_metadata import fetch_default_branch_commit_date, fetch_json
name = os.environ.get("ENTRY_NAME", "")
url = os.environ.get("ENTRY_URL", "")
gh_repo = os.environ.get("ENTRY_GITHUB", "")
description = os.environ.get("ENTRY_DESCRIPTION", "")
yaml_file = os.environ.get("ENTRY_YAML_FILE", "")
token = os.environ.get("GITHUB_TOKEN", "")
yaml_path = Path("data") / f"{yaml_file}.yaml"
if not yaml_path.exists():
print(f"ERROR: {yaml_path} not found", file=sys.stderr)
sys.exit(1)
# Build entry
entry = {"name": name}
if url:
entry["url"] = url
if gh_repo:
entry["github"] = gh_repo
if description:
entry["description"] = description
# Fetch _meta from GitHub API
if gh_repo:
try:
data = fetch_json(
f"https://api.github.com/repos/{gh_repo}",
token,
"awesome-robotics-libraries-bot",
)
meta = {}
if "stargazers_count" in data:
meta["stars"] = data["stargazers_count"]
last_commit = None
default_branch = data.get("default_branch") or ""
if default_branch:
try:
last_commit = fetch_default_branch_commit_date(
gh_repo,
default_branch,
token,
"awesome-robotics-libraries-bot",
)
except Exception as e:
print(
f"WARN: Could not fetch default-branch commit date: {e}",
file=sys.stderr,
)
if not last_commit and data.get("pushed_at"):
last_commit = data["pushed_at"][:10]
if last_commit:
meta["last_commit"] = last_commit
if data.get("archived"):
meta["archived"] = True
if data.get("license") and data["license"].get("spdx_id", "NOASSERTION") != "NOASSERTION":
meta["license"] = data["license"]["spdx_id"]
if data.get("language"):
meta["language"] = data["language"]
if meta:
entry["_meta"] = meta
except Exception as e:
print(f"WARN: Could not fetch metadata: {e}", file=sys.stderr)
# Load existing entries and insert alphabetically
with open(yaml_path, encoding="utf-8") as f:
entries = yaml.safe_load(f) or []
entries.append(entry)
entries.sort(key=lambda e: e["name"].lower())
with open(yaml_path, "w", encoding="utf-8") as f:
yaml.dump(entries, f, default_flow_style=False, allow_unicode=True, sort_keys=False, width=120)
print(f"Added {name} to {yaml_path}")
PYTHON_SCRIPT
- name: Regenerate README
if: success()
run: python3 scripts/generate_readme.py
- name: Validate
if: success()
run: python3 scripts/validate_entries.py
- name: Create PR
if: success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ steps.parse.outputs.issue_number }}
ENTRY_NAME: ${{ steps.parse.outputs.name }}
YAML_FILE: ${{ steps.parse.outputs.yaml_file }}
run: |
BRANCH="add-entry/${{ steps.parse.outputs.yaml_file }}-$(echo '${{ steps.parse.outputs.name }}' | tr '[:upper:] ' '[:lower:]-' | tr -cd 'a-z0-9-')"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add "data/${YAML_FILE}.yaml" README.md
git commit --signoff -m "content: add ${ENTRY_NAME} to ${YAML_FILE}
Closes #${ISSUE_NUMBER}"
git push -u origin "$BRANCH"
gh pr create \
--title "content: add ${ENTRY_NAME}" \
--body "$(cat <<EOF
## Auto-generated from issue #${ISSUE_NUMBER}
Adds **${ENTRY_NAME}** to the \`${YAML_FILE}\` section.
- YAML entry with metadata populated from GitHub API
- README.md regenerated
- Schema validation passed
Closes #${ISSUE_NUMBER}
EOF
)"
- name: Comment on issue
if: success()
env:
ISSUE_NUMBER: ${{ steps.parse.outputs.issue_number }}
uses: actions/github-script@v9
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.ISSUE_NUMBER),
body: [
'🎉 Great news! A PR has been automatically created to add this entry to the list.',
'',
'A maintainer will review and merge it shortly. Thanks for contributing!',
].join('\n')
});
The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: Add Accepted Entry on: issues: types: [labeled] workflow_dispatch: inputs: issue_number: description: Issue number to process required: true type: number jobs: add-entry: timeout-minutes: 30 if: | (github.event_name == 'issues' && github.event.label.name == 'accepted') || github.event_name == 'workflow_dispatch' runs-on: latchkey-small permissions: contents: write pull-requests: write issues: write steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: cache: 'pip' python-version: "3.12" - run: pip install pyyaml jsonschema - name: Parse issue and build entry id: parse env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} uses: actions/github-script@v9 with: script: | const owner = context.repo.owner; const repo = context.repo.repo; const issueNumber = context.eventName === 'workflow_dispatch' ? Number('${{ inputs.issue_number }}') : context.issue.number; const issueData = context.eventName === 'workflow_dispatch' ? (await github.rest.issues.get({ owner, repo, issue_number: issueNumber, })).data : context.payload.issue; const body = issueData.body || ''; function normalizeGitHubRepo(value) { if (!value) return ''; const trimmed = value.trim(); const slugMatch = trimmed.match(/^(?:https?:\/\/github\.com\/)?([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+?)(?:\.git|\/)?$/); return slugMatch ? slugMatch[1] : ''; } function extractField(body, header) { const re = new RegExp(`### ${header}\\s*\\n\\s*\\n?\\s*([\\s\\S]*?)(?=\\n###|$)`); const m = body.match(re); return m ? m[1].trim() : ''; } const labels = (issueData.labels || []).map(label => typeof label === 'string' ? label : label.name); if (!labels.includes('accepted')) { core.setFailed(`Issue #${issueNumber} does not have the accepted label`); return; } const name = extractField(body, 'Resource Name') || extractField(body, 'Library Name'); const url = extractField(body, 'URL'); const category = extractField(body, 'Category'); const description = extractField(body, 'Description'); // GitHub repo: from structured field or from URL let ghRepo = normalizeGitHubRepo(extractField(body, 'GitHub Repository')); if (!ghRepo) { const urlMatch = body.match(/github\.com\/([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+)/); if (urlMatch) ghRepo = urlMatch[1]; } if (!name || !category) { core.setFailed('Could not parse required fields (name, category) from issue body'); return; } // Map category to YAML filename const categoryMap = { 'Dynamics Simulation': 'dynamics-simulation', 'Inverse Kinematics': 'inverse-kinematics', 'Machine Learning': 'machine-learning', 'Motion Planning and Control': 'motion-planning', 'Optimization': 'optimization', 'Robot Modeling': 'robot-modeling', 'Robot Platform': 'robot-platform', 'Reinforcement Learning for Robotics': 'reinforcement-learning', 'SLAM': 'slam', 'Vision': 'vision', 'Fluid': 'fluid', 'Grasping': 'grasping', 'Humanoid Robotics': 'humanoid-robotics', 'Multiphysics': 'multiphysics', 'Math': 'math', 'ETC': 'etc', 'Simulators': 'simulators', 'Other Awesome Lists': 'other-awesome-lists', }; const yamlFile = categoryMap[category]; if (!yamlFile) { core.setFailed(`Unknown category: ${category}`); return; } core.setOutput('name', name); core.setOutput('url', url); core.setOutput('github_repo', ghRepo); core.setOutput('category', category); core.setOutput('description', description); core.setOutput('yaml_file', yamlFile); core.setOutput('issue_number', issueNumber); - name: Fetch metadata and add YAML entry if: success() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ENTRY_NAME: ${{ steps.parse.outputs.name }} ENTRY_URL: ${{ steps.parse.outputs.url }} ENTRY_GITHUB: ${{ steps.parse.outputs.github_repo }} ENTRY_DESCRIPTION: ${{ steps.parse.outputs.description }} ENTRY_YAML_FILE: ${{ steps.parse.outputs.yaml_file }} run: | python3 - <<'PYTHON_SCRIPT' import os import sys from pathlib import Path import yaml from scripts.github_metadata import fetch_default_branch_commit_date, fetch_json name = os.environ.get("ENTRY_NAME", "") url = os.environ.get("ENTRY_URL", "") gh_repo = os.environ.get("ENTRY_GITHUB", "") description = os.environ.get("ENTRY_DESCRIPTION", "") yaml_file = os.environ.get("ENTRY_YAML_FILE", "") token = os.environ.get("GITHUB_TOKEN", "") yaml_path = Path("data") / f"{yaml_file}.yaml" if not yaml_path.exists(): print(f"ERROR: {yaml_path} not found", file=sys.stderr) sys.exit(1) # Build entry entry = {"name": name} if url: entry["url"] = url if gh_repo: entry["github"] = gh_repo if description: entry["description"] = description # Fetch _meta from GitHub API if gh_repo: try: data = fetch_json( f"https://api.github.com/repos/{gh_repo}", token, "awesome-robotics-libraries-bot", ) meta = {} if "stargazers_count" in data: meta["stars"] = data["stargazers_count"] last_commit = None default_branch = data.get("default_branch") or "" if default_branch: try: last_commit = fetch_default_branch_commit_date( gh_repo, default_branch, token, "awesome-robotics-libraries-bot", ) except Exception as e: print( f"WARN: Could not fetch default-branch commit date: {e}", file=sys.stderr, ) if not last_commit and data.get("pushed_at"): last_commit = data["pushed_at"][:10] if last_commit: meta["last_commit"] = last_commit if data.get("archived"): meta["archived"] = True if data.get("license") and data["license"].get("spdx_id", "NOASSERTION") != "NOASSERTION": meta["license"] = data["license"]["spdx_id"] if data.get("language"): meta["language"] = data["language"] if meta: entry["_meta"] = meta except Exception as e: print(f"WARN: Could not fetch metadata: {e}", file=sys.stderr) # Load existing entries and insert alphabetically with open(yaml_path, encoding="utf-8") as f: entries = yaml.safe_load(f) or [] entries.append(entry) entries.sort(key=lambda e: e["name"].lower()) with open(yaml_path, "w", encoding="utf-8") as f: yaml.dump(entries, f, default_flow_style=False, allow_unicode=True, sort_keys=False, width=120) print(f"Added {name} to {yaml_path}") PYTHON_SCRIPT - name: Regenerate README if: success() run: python3 scripts/generate_readme.py - name: Validate if: success() run: python3 scripts/validate_entries.py - name: Create PR if: success() env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_NUMBER: ${{ steps.parse.outputs.issue_number }} ENTRY_NAME: ${{ steps.parse.outputs.name }} YAML_FILE: ${{ steps.parse.outputs.yaml_file }} run: | BRANCH="add-entry/${{ steps.parse.outputs.yaml_file }}-$(echo '${{ steps.parse.outputs.name }}' | tr '[:upper:] ' '[:lower:]-' | tr -cd 'a-z0-9-')" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout -b "$BRANCH" git add "data/${YAML_FILE}.yaml" README.md git commit --signoff -m "content: add ${ENTRY_NAME} to ${YAML_FILE} Closes #${ISSUE_NUMBER}" git push -u origin "$BRANCH" gh pr create \ --title "content: add ${ENTRY_NAME}" \ --body "$(cat <<EOF ## Auto-generated from issue #${ISSUE_NUMBER} Adds **${ENTRY_NAME}** to the \`${YAML_FILE}\` section. - YAML entry with metadata populated from GitHub API - README.md regenerated - Schema validation passed Closes #${ISSUE_NUMBER} EOF )" - name: Comment on issue if: success() env: ISSUE_NUMBER: ${{ steps.parse.outputs.issue_number }} uses: actions/github-script@v9 with: script: | await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: Number(process.env.ISSUE_NUMBER), body: [ '🎉 Great news! A PR has been automatically created to add this entry to the list.', '', 'A maintainer will review and merge it shortly. Thanks for contributing!', ].join('\n') });
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.