Skip to content

Catalog Updater

The catalog updater is a verify-first analog to Dependabot for Claude Code plugin marketplaces. It resolves each external plugin entry to its latest release, verifies that release’s attestations fail-closed, and opens a zero-touch auto-merge PR in the target repo. A release that fails attestation verification is skipped and logged; it is never re-pinned.

The system has three parts:

ComponentRole
plugin-catalog-update-hub.ymlScheduled hub: discover target marketplace repos → matrix → per-repo update run
.github/actions/plugin-catalog-update/Composite action and Python engine (catalog_update.py): resolve release, verify-first, re-pin, open PR, auto-merge. verify mode is shared by catalog-admission.
catalog-update/deny-list.yamlOptional safety valve — repos the hub must never touch

Runs on a Monday schedule (cron 37 6 * * 1) and via workflow_dispatch. Discovers all marketplace repos accessible to the attested-delivery-ci App, excludes repos in deny-list.yaml, excludes repos without .claude-plugin/marketplace.json, and fans out a matrix of per-repo update jobs.

Opt-in mechanism: installing the attested-delivery-ci App on a repo opts it into discovery. Uninstalling the App, or adding the owner/repo to deny-list.yaml, opts it out. There is no additional registry to maintain.

EventNotes
schedule37 6 * * 1 — Mondays, scattered minute (never :00)
workflow_dispatchSupports dry-run and repo inputs
NameTypeDefaultDescription
dry-runbooleanfalseResolve, verify, and render the PR body to the run log, but open and merge nothing
repostring''Limit the run to a single owner/repo

Top-level contents: read. The discover job needs only contents: read; write operations are performed by the App token, not the workflow’s GITHUB_TOKEN.

The hub authenticates as the attested-delivery-ci GitHub App. The App client id (Iv23liQchSZyQeuGkhWi) is a public identifier pinned directly in the workflow. The only credential in org config is the private key, stored as an org secret:

Terminal window
gh secret set CATALOG_UPDATER_APP_PRIVATE_KEY --org attested-delivery \
--visibility selected --repos .github < ~/.secrets/attested-delivery-ci.pem

The App installation token is minted per-job and scoped to the target repo (least privilege).

actions/create-github-app-token — GitHub-created, confirm it is permitted.

Terminal window
gh workflow run plugin-catalog-update-hub.yml -f dry-run=true

The shared engine used by both the hub (mode: update) and catalog-admission (mode: verify). Implemented as a composite action wrapping catalog_update.py (stdlib Python, shells to gh).

Location: .github/actions/plugin-catalog-update/action.yml

NameRequiredDefaultDescription
modenoupdateupdate (resolve latest release, verify attestations, re-pin, open PR) or verify (fail-closed verify of the currently pinned ref only)
repoyesowner/repo of the marketplace being processed
marketplace-pathno.claude-plugin/marketplace.jsonPath to marketplace.json within the checkout
predicate-typesnohttps://slsa.dev/provenance/v1Required predicates: one "<uri> [signer-workflow]" per line. Empty fails closed.
github-tokenyesToken for gh (App installation token in the hub; GITHUB_TOKEN in admission)
dry-runnofalseResolve, verify, and render but open and merge nothing

For each external plugin entry in marketplace.json:

  1. Resolve the source repo’s latest release (releases/latest). Entries with no release are skipped.
  2. Dereference the release tag to a commit SHA (commits/<tag> — handles annotated tags). Entries already at the latest SHA are skipped.
  3. Verify the release’s attestations fail-closed (gh attestation verify). Every required predicate must verify; a release that fails is skipped and logged.
  4. Re-pin source.sha and source.ref (surgical text edit — formatting preserved).
  5. Open an auto-merge PR in the target repo on branch deps/external-plugin/<name>. The PR body carries the full attestation evidence.

The target repo’s catalog-admission gate re-verifies on the PR and is the merge control.

Fail-closed verification of every external entry at its currently pinned ref. Used by catalog-admission workflows in target repos. Exits non-zero if any entry’s attestations do not verify.

The predicate-types input accepts one predicate specification per line in the form:

<uri> [signer-workflow]

The default is SLSA build provenance verified by --repo:

https://slsa.dev/provenance/v1

To require seam-signed gate verdicts, add lines with the signer workflow:

https://slsa.dev/provenance/v1
https://attested-delivery.github.io/attestations/sast/v1 attested-delivery/.github/.github/workflows/reusable-attest-scan.yml
https://attested-delivery.github.io/attestations/sca/v1 attested-delivery/.github/.github/workflows/reusable-attest-scan.yml

The same set should be required in the target’s catalog-admission so the hub and the gate agree.

On each target repo: configure the attested-delivery-ci App actor to bypass the required human review rule on deps/external-plugin/* branches, while keeping catalog-admission and the required quality gates as required checks. The fail-closed gate is then the sole merge control.


Location: catalog-update/deny-list.yaml in the .github repo.

Format: a YAML list under a deny: key. One owner/repo per entry.

deny:
- attested-delivery/example-repo

Repos on the deny-list are skipped by the hub during discovery, even when the App is installed. Use this as a safety valve when the App is installed org-wide but a specific repo should not receive automated catalog updates.


Discussion

Comments are powered by GitHub Discussions. Sign in with GitHub to ask a question or share how this applies in your org.