---
title: "Importing and reviewing skill sources"
manual: "LLM Foundation"
version: "main"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:importing-and-reviewing-skill-sources@main"
source: "Administration/SkillSources.rst"
rendered: "2026-11-12T15:41:35+00:00"
---

# Importing and reviewing skill sources {#importing-and-reviewing-skill-sources}

## Source types {#administration-skills-source-types}

Imported sources have three types; local backend authoring is the fourth
way to create a skill and does not require a source:

-   **`single_file`**

    One `SKILL.md` at a fixed path in a repository. A single, explicit
    admin act — its skill may default to enabled.

-   **`repo`**

    A whole repository. Every `SKILL.md` under the repo root,
    `skills/<name>/`, `.claude/skills/<name>/` or
    `<plugin>/skills/<name>/` is discovered. Discovered skills arrive
    **disabled** for review.

-   **`marketplace`**

    An Anthropic `marketplace.json` index that lists plugins pointing at
    further repositories. Each entry is expanded with the `repo` flow.
    All discovered skills arrive **disabled**.

    The parser accepts a `plugins` array. A plugin source can be an
    `owner/repository` string, an object with a string `repo` of that
    form, or an object with a GitHub `url`. A string `repo` takes
    precedence over `url`. GitHub URL host matching is case-insensitive;
    the first two path segments identify the repository and a trailing
    `.git` suffix is removed. Unsupported entries are skipped while
    later supported entries are still processed. A plugin's string `ref`
    is passed to its repository sync; an absent or non-string `ref` uses
    `HEAD`. An empty string is normalized to `HEAD` by that sync.

## Adding a source {#administration-skills-add}

1.  Navigate to **AI > Authoring > Skills**.
1.  Click **New Skill Source**.
1.  Fill in the fields:

    -   ****Title****

        Display name for the source list.

    -   ****Type****

        `single_file`, `repo` or `marketplace` (see above).

    -   ****URL****

        The GitHub URL the type expects (the `SKILL.md` URL, the
        repository URL, or the `marketplace.json` URL). For a marketplace,
        a plain repository URL uses `.claude-plugin/marketplace.json`
        at `HEAD`; a raw or blob index URL specifies its own path and ref.

    -   ****Ref****

        For `single_file` and `repo`, a branch, tag or commit (for
        example `main` or `v1.2.0`); an empty value uses `HEAD`.
        It is resolved once to an immutable commit SHA, and the skill
        bodies are fetched by that SHA.

        A `marketplace` source does not use this field. A raw or blob
        index URL is fetched at the URL's ref. A plain repository URL
        instead resolves `HEAD` and fetches the conventional index path
        by that commit. Each plugin repository is resolved separately from
        that plugin's `ref` or `HEAD`, and its skill bodies use the
        resulting commit.
1.  Click **Save**.

The `pinned_sha`, `sync_status`, `sync_error` and `last_synced`
fields are managed by the sync run and shown read-only.

Sync errors shown by the module and stored on the source mask credential
query parameters and URL userinfo passwords. Fatal-error diagnostics retain
the exception class, source uid and sanitized message and source URL; they
do not retain the raw exception object.

## GitHub token and rate limits {#administration-skills-token}

Unauthenticated GitHub API access is limited to **60 requests per hour**,
which is quickly exhausted by a `repo` or `marketplace` sync. Add a
personal access token (a read-only, public-repo token is enough) to raise
the limit and to read private repositories.

-   The token is set through the **Set token** action on a source,
    **not** typed into a FormEngine field. The source stores a vault reference;
    nr-vault stores the token with envelope encryption. The plaintext token
    is never stored in the source record.
-   When a sync hits the rate limit (HTTP 403 with no remaining quota), the
    source is set to `sync_status = error` carrying the reset time; state
    is not partially corrupted. Add a token and re-sync.

## Host-allowlist prerequisite {#administration-skills-allowlist}

nr-llm enforces an **app-level GitHub allowlist** on every skill request:
the scheme must be `https` and the host must be one of `github.com`,
`raw.githubusercontent.com`, `api.github.com` or
`codeload.github.com`. This is separate from, and in
addition to, the nr-vault SSRF guard.

On hardened instances that restrict outbound HTTP through the global
`HTTP/allowed_hosts` SSRF setting, those four GitHub hosts **must be on
that list**, otherwise every sync fails closed. This is a deliberate
prerequisite — nr-llm never silently bypasses the SSRF guard.

## Syncing and the review flow {#administration-skills-review}

![The Skills module showing a synced marketplace source and the discovered skills with their support badge and enabled state](../Images/SkillsModule.png)

1.  On a source, click **Sync**. The source moves through
    `never_synced` → `syncing` → `ok` / `partial` / `error`.
    Synchronization requires a saved, non-deleted source. The database row
    carries an opaque lease owner, so a second caller is refused even when its
    loaded source still has an older status. A missing or soft-deleted source
    returns `error` before remote requests or skill writes.
1.  `partial` means the per-sync file-count or wall-time bound was
    reached (large marketplaces); the skills fetched so far are stored.
1.  Discovered skills from `repo` and `marketplace` sources are
    created **disabled by default**. Review each one, then toggle it on
    with **Enable**.
1.  **Re-sync never silently changes an enabled skill.** If a re-sync
    brings a different version of an enabled skill, nr-llm **auto-disables
    it** so you re-confirm before it is used again. A version is the body
    *and* the frontmatter fields the model reads — name, description,
    support status, `allowed-tools` and the `process` marker — so a
    frontmatter-only change upstream, such as a widened `allowed-tools`,
    disables the skill as well ([ADR-214](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-214@main)). The `page` key
    is not part of the version: a change of it alone is stored and disables
    nothing ([Whether a skill works on a page](https://docs.typo3.org/permalink/netresearch/nr-llm:administration-skills-page@main)).
1.  A skill that disappeared upstream is marked **orphaned and disabled**,
    never silently dropped, so attachments do not vanish.

The source, skill and skill-audit tables must use the same database connection.
Remote collection runs outside the publication transaction. A conditional
ownership check then fences skill changes, orphaning, audit writes and source
completion in one short transaction. Losing the lease during a slow remote
fetch returns `error` and publishes none of the collected changes; an older
worker cannot clear a replacement owner's lease. A publication failure rolls
back its changes and reports zero change counters.

A `syncing` row is interrupted when its heartbeat is zero or differs from
the current time by more than 180 seconds, including excessive future clock
skew. The exact 180-second boundary is still live. Opening the source list
rechecks the persisted row and changes an interrupted lease to retryable
`error`; a renewed or completed row is preserved. Trigger **Sync**
again to retry. Lease ownership fields stay internal and are not editable in
FormEngine ([ADR-221](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-221@main)).

GitHub may mark an oversized recursive tree response as `truncated`.
Such a listing is rejected before any skill file is fetched: it cannot prove
that omitted paths were removed. A `repo` source reports `error` and
preserves its existing skills and pinned SHA. For a `marketplace` source,
the affected child repository reports a partial sync; its existing skills
remain unchanged, and other reachable child repositories can still sync.

Malformed GitHub JSON responses are refused with the client's typed response
error. Diagnostic logging is best effort and cannot replace that refusal.

Deleting a source cascade-deletes its skills.
