Skip to content

CI ​

Nostr CI is a protocol, not a product, and this page is the guide to using it. Read Understanding Nostr CI first if you can. If not, the next three paragraphs are the short version.

Nostr CI replaces a traditional forge's built-in CI server with a coordinator you choose. The coordinator watches your repository for triggers: pushes, pull requests, and manual runs. When a workflow in .ngit/<runner-format>/workflows/ matches, it hands each job to an appropriate, available compute provider that fits the job's needs and your preferences. When the jobs finish, it publishes the final result as a signed Nostr event. Compatible clients like ngit and GitWorkshop read that event and show you the outcome with its trust context.

That is the intended model. Today the coordinator also acts as the compute provider, running every job on its own host through act, the one runner format shipped so far, which reads GitHub Actions-compatible workflows from .ngit/act/workflows/ on Linux. macOS and Windows jobs need another CI system for now; the roadmap covers where this is going. The coordinators you can use are self-hosted: yours, or one an operator has agreed to point at your repository, with any payment settled between you out of band.

The intended funding model is that maintainers or sponsors pay coordinators, for example by subscription, a funding pot that gets topped up, or both, and coordinators pay compute providers. Who pays describes it.

1. Choose a coordinator ​

Open your repository's coordinator directory on GitWorkshop:

text
https://gitworkshop.dev/<OWNER>/<REPO>/actions/coordinators

<OWNER> is the maintainer's npub1... or NIP-05 address. The page lists only the coordinators that have advertised themselves as available for this specific repository, not every coordinator on the network:

BadgeMeaning
WatchingIt is acting for this repository now
Ready for this repoIt will start once a maintainer requests service
OfflineIts advertisement has expired; past runs are still shown

Billing is arranged out of band today, so a coordinator appears here only after its operator has agreed to serve your repository, whether for free or on terms you settled directly. An operator's reputation is often tied to the GRASP service they also run. If the directory is empty, nobody has offered to run CI for this repository yet. Ask an operator you trust to add it, or run a coordinator yourself.

2. Request service ​

A request is a standing instruction to one coordinator for one repository. It covers runs the coordinator starts after it and stays in force until you stop it.

On GitWorkshop, a confirmed maintainer presses Request coordinator service on the coordinator's page. From the CLI:

bash
ngit ci request <COORDINATOR>     # npub or hex
ngit ci stop <COORDINATOR>

Request as a confirmed maintainer of the repository. A coordinator's default policy ignores requests from anyone else; for that case, see Run CI for a repository you do not maintain.

Confirmation arrives as a signed Repository Status from the coordinator. On GitWorkshop the badge changes to Watching and the coordinator page lists the workflow paths it will execute and the secret names it holds.

3. Add a workflow ​

text
.ngit/act/workflows/ci.yml
yaml
name: Tests

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make test

Keep runs-on within the labels your coordinator advertises. A file that asks for an unsupported label is skipped silently. If a job needs ngit or a nostr:// remote, add the setup action, which also works on GitHub:

yaml
- uses: danconwaydev/setup-ngit@v3

Nostr CI reads only .ngit/

A coordinator runs workflows from .ngit/<runner-format>/workflows/ and never from .github/ or .gitlab/. Workflows for another CI system keep running untouched alongside it. That helps during a migration, and it covers macOS or Windows jobs that Nostr CI cannot run yet.

See act workflow files for how this differs from GitHub Actions syntax and what is supported.

Publish an nsite preview

A pull-request job can publish its build with a fresh, disposable identity and expose the URL through a public job output named nsite or beginning with nsite_, such as nsite_preview. GitWorkshop recognises that convention and offers the URL as a PR preview. Keep maintainer signing keys out of the job; see Public outputs and PR previews for the output contract and security boundary.

Push, and the run appears against that commit.

4. Read the results ​

On GitWorkshop, status icons sit beside commits, branches, tags, and PRs. The Actions tab lists every run for the repository, and the Checks panel on a commit or PR expands each run into its jobs, timings, coordinator, log tail, a link to the full log for each job, artifacts, and public outputs. Every run carries a trust label and the evidence behind it.

From the CLI:

bash
ngit ci status <COMMIT-ISH>
ngit ci status <COMMIT-ISH> --offline    # cache only, after the first query
ngit ci status <COMMIT-ISH> --log-tail all

The target can be a commit-ish, a PR (#<prefix>, an nevent, or a full event id), or nothing for HEAD. Query the commit that introduced the change, not just the branch tip. A PR reports only the runs for its latest revision.

The response includes, for every run against the target:

  • its state and conclusion
  • the workflow path that produced it
  • its trust level and the evidence behind it, such as which maintainer requested the coordinator
  • an integrity check: whether the commit is present locally and the workflow file at that commit hashes to what the coordinator signed
  • each job with its conclusion, the compute provider that executed it, a link to its full log, and a log tail for failed jobs

Signed per-job log tails are included for non-successful jobs by default. --log-tail all includes successful jobs too, while --log-tail none omits all tails. The selection applies to both human and JSON output.

For automation, add --json to get the same information as one document. Each run's trust level is ci.runs[].classification, and maintainer-directed means a maintainer asked for it.

Two different successes

command_status: ok means ngit answered the question. Whether CI passed is ci.conclusion, and only once ci.state is concluded. Partial coverage is not evidence of success either.

When no run appears ​

Report it as "no matching CI event found" rather than as a pass. Then check, in order:

  1. Is a coordinator Watching the repository, and does it support the workflow's runs-on labels?
  2. Did the workflow exist at that commit, and did its trigger match the event?
  3. Did the query refresh the repository relays, or was it --offline against a cold cache?

Only after all three should you conclude that CI genuinely did not run. A workflow the coordinator refused, for example one declaring its own container:, produces a startup_failure result rather than silence.

5. Gate a merge on CI ​

To make a command exit non-zero unless CI is green and meets a trust floor:

bash
ngit ci status <COMMIT-ISH> \
  --json \
  --require-ci-trust maintainer-directed
LevelMeaning
maintainer-directedThe result traces back to a maintainer's request for this coordinator or this run
operationally-associatedA weaker link to infrastructure the repository lists or to a recognised coordinator

The gate passes only if the result is a success and its weakest run meets the floor. It works on a merge, which is where it matters most:

bash
ngit pr merge <ID> --require-ci-trust maintainer-directed
git push origin <TARGET-BRANCH>

ngit pr merge selects the PR's declared target, or the repository default, and creates the local no-ff merge commit there.

Without the flag, a failing, unfinished, or absent result will not block the merge, and neither will a signer with no known trust context. A successful push does not enforce CI by itself.

6. Run a workflow manually ​

A Manual Trigger asks a coordinator to run one workflow once, for one commit. It needs no standing request, and it replays a push or pull_request workflow even though the file declares no manual trigger, which is how you retry after a coordinator was offline.

bash
ngit ci trigger <COORDINATOR> --workflow .ngit/act/workflows/ci.yml
ngit ci trigger <COORDINATOR> <COMMIT-ISH> --workflow .ngit/act/workflows/ci.yml
ngit ci trigger <COORDINATOR> --workflow .ngit/act/workflows/ci.yml --ref refs/heads/main

The workflow is identified by the SHA-256 of the file at the resolved commit, never your working tree, whose line endings or filters may hash differently from what the coordinator sees. --ref supplies the Git ref a workflow can read as context.

On GitWorkshop, a maintainer presses Retry workflow on any completed run, which publishes the same event for that run's commit and workflow.

7. Configure secrets ​

Workflows read values through the secrets context, for example secrets.DEPLOY_TOKEN. A coordinator injects a secret only when the run's trigger was authored by a confirmed maintainer of the repository. Third-party pull requests always run with empty secrets, so nothing a PR needs can depend on one. How secrets stay with maintainers explains the model, and the act workflow files reference gives the naming rules.

Maintainers register secrets on GitWorkshop from Manage secrets on the coordinator's page. Each value is encrypted to the coordinator and sent over Nostr, and it never leaves the coordinator again. For easy management, the coordinator publishes the names in use, with who added each and when, and GitWorkshop shows them as "Secrets in use".

Optionally, a maintainer can ask the coordinator to store their repository's values at rest only encrypted to a remote signer they supply, and to decrypt them on demand through that signer for each run. GitWorkshop marks those "Bunker-sealed". If the signer is unreachable, an authorised run fails with startup_failure rather than running without its secrets. All of this is managed from GitWorkshop; there is no ngit ci command for secrets in this release.

An operator can also set values directly in the coordinator's environment or as systemd credentials; see coordinator secret sources. An operator value shadows a maintainer value of the same name.

Run a coordinator yourself ​

Everything above is for maintainers and contributors. To offer CI, see Run ngit-ci and its generated configuration reference.

Run CI for a repository you do not maintain ​

The protocol does not limit who may run workflows against a repository. Any coordinator can watch any repository and publish signed results, and every reader decides what those results are worth. The steps above follow the maintainer because a coordinator's default policy acts only on a maintainer's request, and only a maintainer's request earns the maintainer-directed trust label.

That leaves room for CI nobody asked for. You might run a coordinator that builds release binaries for projects you depend on and checks them against the published ones, or run a project's test suite on hardware its maintainers do not have. To do that:

  1. Run your own coordinator and admit the repository with --repos. Admission is the operator's decision; the maintainer is not consulted.
  2. Decide how ordinary pushes and PRs start. --execution-policy automatic runs them without any request. Alternatively keep request-required, list your own key in --additional-requesters, and request service with ngit ci request as usual. ngit warns that you are not a maintainer and proceeds.
  3. Read results with ngit ci status and on GitWorkshop exactly as a maintainer would.

Results from your coordinator carry your signature and no maintainer authorisation. Other readers see them as No known context, or as Seen in your network on GitWorkshop if they follow you, and they never satisfy a --require-ci-trust floor. See the trust levels for what each label means.

Next ​