Sync Plugins from GitHub
Import plugins from one or more external GitHub repositories into your organization, and keep them fresh automatically. Point Willow at a repo that contains plugins, and Willow discovers every plugin in it, imports them, and (optionally) re-imports them once a day so your marketplace always reflects the source repository.
This is the inbound counterpart to publishing: GitHub Integration and Managed Marketplace Sync push your plugins to GitHub, whereas source repos pull plugins from GitHub into Willow.
Go to Plugins > Plugins Settings (or select the gear icon on the plugins list page) and open the Sync from GitHub card.
How it works
- You register one or more source repositories and pick the authentication mode used to read each one.
- On demand — or on a daily schedule — Willow discovers the plugins in each repo and imports them.
- Imported plugins are then republished to your organization's marketplace targets (the managed repo and/or your own GitHub Integration), so downstream hosts (Cursor, Claude, GitHub Copilot, Codex) pick them up.
Plugins are discovered via the repository's .claude-plugin/marketplace.json, falling back to scanning the plugins/ folder if no manifest is present. All plugins in the repo are imported together — you do not select individual plugins for a source repo.
Each repo is processed in isolation, so a failure syncing one repo never blocks the others.
Add a source repo
- In the Sync from GitHub card, select Add repo.
- Choose an Authentication mode (see Authentication modes below).
- Enter the repository in
owner/repoformat (a fullhttps://github.com/owner/repoURL also works). - Select Add repo.
The repo is saved to your source list along with the auth mode you selected. Nothing is imported yet — use Sync (or wait for the daily auto-sync) to import its plugins.
Authentication modes
The authentication mode is chosen per repo when you add it, and it is reused for every later sync (manual and scheduled).
| Mode | When to use | Notes |
|---|---|---|
| Public | Public repositories | No credentials required. Subject to GitHub's unauthenticated rate limit (~60 requests/hour). |
| Personal GitHub OAuth | Private repos you can access with your own account | Requires connecting your GitHub account. Raises the rate limit to ~5,000 requests/hour. Only offered when GitHub OAuth is configured for your deployment. |
| Organization GitHub App | Private repos your org's GitHub App can access | Uses your organization's configured GitHub App. Only offered when an org GitHub App is set up. |
If neither OAuth nor an org GitHub App is configured, repos are read in Public mode.
Grant the OAuth app access to a private organization repo
When you use Personal GitHub OAuth to read a repository owned by a GitHub organization, connecting your account is not always enough. Even if your account can see the repo, GitHub only lets the OAuth app read the organization's private repositories when both of these are satisfied:
- Third-party OAuth app access — the organization must have approved the app (many orgs enable OAuth App access restrictions, which block third-party apps until an owner grants them). Until it is approved, GitHub hides the org's private repos from the app and returns "not found", which Willow surfaces as Access denied — unable to read this repository.
- SAML SSO authorization — if the organization enforces SAML single sign-on, your OAuth authorization must additionally be authorized for that org.
To fix it:
- When Willow shows the "This looks like a private organization repo" notice during import, select Allow on GitHub. (You can also go there directly: GitHub → Settings → Applications → Authorized OAuth Apps → Willow.)
- Find the organization in the app's Organization access list and click Grant — or Request if you are not an owner, which sends an approval request to an organization owner.
- If the organization enforces SAML SSO, authorize the app for the organization on the same page.
- Return to Willow and try the import again.
If your organization has a configured GitHub App, switch the Authentication mode to Organization GitHub App. A GitHub App is installation-scoped, so it reads the org's private repos without depending on any individual's OAuth grant or SSO session — no per-user approval needed.
Per-repo controls
Each source repo row exposes:
- Sync — imports all of that repo's plugins right now, ignoring the daily schedule and the once-per-day guard.
- Remove — removes the repo from your source list (already-imported plugins are left in place).
- Auto-sync daily — a toggle that opts the repo into the daily auto-sync. When on, all plugins in the repo are re-imported automatically each day.
The row also shows the last result at a glance: Synced <date> on success, or Last sync failed (hover for the error) when the most recent sync errored.
Use Sync all now in the card header to sync every configured repo immediately.
Daily auto-sync
Turn on Auto-sync daily for a repo and Willow re-imports it automatically, so plugins stay in sync with the source repository without manual work.
- The scheduler attempts the auto-sync twice a day (at 00:00 and 12:00 UTC). Running twice is purely for resilience — a missed or failed morning run is retried in the evening.
- Each repo still syncs at most once per UTC day. Once a repo has synced successfully today, it is skipped until tomorrow.
- Unchanged repos are skipped. If the repository's latest commit is the same as the commit recorded at the last sync, the repo is skipped — nothing is re-imported when there is nothing new. (If the current commit cannot be determined, the sync runs anyway.)
- Only repos with the Auto-sync daily toggle enabled are included. The on-demand Sync / Sync all now actions ignore both the toggle and the once-per-day guard.
After a successful auto-sync that imported at least one plugin, Willow republishes the org's marketplace so the changes propagate to your configured GitHub targets.
Trigger a sync via the API
The Sync all now action is also available as a token-authenticated endpoint, so you can trigger a re-import from CI/CD, a scheduled job, or a service account instead of clicking the button. It behaves exactly like the manual action: it ignores the per-repo Auto-sync daily toggle and the once-per-day guard, and re-imports using the credentials captured when each repo was added.
Endpoint
POST /api/plugins/sync
Authentication — an organization API token with the admin:write scope, passed as a bearer token. (Generate one under Admin > API Tokens.)
Body (all fields optional, application/json):
| Field | Type | Description |
|---|---|---|
repo | string | Sync only this single source repo, in owner/repo form (mirrors the per-repo Sync button). Omit to sync every configured repo. |
background | boolean | When true, the request returns 202 immediately and the sync continues server-side. Use this for orgs with many repos, where waiting for the full sync could exceed your client's request timeout. Defaults to false (wait and return per-repo results). |
Sync every configured repo and wait for the result:
curl -X POST \
-H "Authorization: Bearer wxt_xxxxx" \
-H "Content-Type: application/json" \
-d '{}' \
https://app.eu.withwillow.ai/api/plugins/sync
{
"data": {
"syncedAny": true,
"outcomes": [
{ "repo": "my-org/my-plugins", "status": "success", "imported": 4, "skipped": 0, "failed": 0 }
]
}
}
Each entry in outcomes reports the repo, a status (success, partial, error, or skipped), and how many plugins were imported / skipped / failed. When a repo errors, the entry also includes an error message.
Sync a single repo:
curl -X POST \
-H "Authorization: Bearer wxt_xxxxx" \
-H "Content-Type: application/json" \
-d '{"repo":"my-org/my-plugins"}' \
https://app.eu.withwillow.ai/api/plugins/sync
Fire-and-forget (return immediately):
curl -X POST \
-H "Authorization: Bearer wxt_xxxxx" \
-H "Content-Type: application/json" \
-d '{"background":true}' \
https://app.eu.withwillow.ai/api/plugins/sync
{ "started": true }
Use your organization's app host. For EU tenants that is https://app.eu.withwillow.ai; other tenants use their own Willow app domain.
Recent sync activity
When a plugin source repo is configured, the card shows a Recent sync activity list of the most recent auto-sync and "Sync all" runs. Each entry shows:
- the repository,
- the number of plugins imported,
- a status icon — success, partial (some plugins failed), or error,
- an error message when the run failed, and
- the timestamp.
Use Refresh to reload the list. Viewing sync activity requires monitor-read permission; if you do not have it, the activity list is simply hidden.
Troubleshooting
- No plugins found — the repo must expose plugins via
.claude-plugin/marketplace.jsonor aplugins/folder. Confirm the structure and the branch you expect Willow to read. - Authentication failed / 401 — for Personal GitHub OAuth, reconnect your GitHub account; for Organization GitHub App, confirm the app is installed on the repo. Public mode cannot read private repositories.
- Repository access denied / 403 — you are using Personal GitHub OAuth on a private organization repo the OAuth app has not been granted access to. Grant (or request) organization access for the app — and authorize it for SAML SSO if the org enforces it. See Grant the OAuth app access to a private organization repo, or switch to the Organization GitHub App mode. This applies to scheduled auto-syncs too: an access-denied auto-sync is reported as a failed run (not a silent "0 imported"), and Recent sync activity shows an Allow on GitHub link to fix it.
- Hitting rate limits — Public mode is limited to ~60 requests/hour. Switch to OAuth or the org GitHub App to raise the limit to ~5,000/hour.
- Auto-sync did not run — confirm the Auto-sync daily toggle is on. Remember the repo is skipped if it already synced today or if its latest commit is unchanged. Use Sync to force an immediate import.
- Partial status — some plugins in the repo failed to import while others succeeded. Check Recent sync activity for the failing plugin names and errors.