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.

ngit-ci supports two workflow families. act reads GitHub Actions-compatible workflows from .ngit/act/workflows/ and runs Linux container jobs through the coordinator's execution backend. Nix, an experimental opt-in, reads .ngit/nix/workflows/ and delegates jobs to separately operated providers. The coordinator can schedule work without executing any jobs itself. Providers advertise their native Nix systems, isolation, and free capacity; availability of a system depends on the workers your operator trusts. The coordinators you can use are self-hosted, with billing arranged 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 ​

Choose act workflow files for existing Actions-style steps, or Nix workflow files for pinned flake builds and jobs across remote workers. Both families can coexist, but matching workflows run independently: remove overlapping triggers when migrating to avoid duplicate builds.

Nix: builds, tests, and dependent jobs ​

For a complete flake you can run locally, start with Build and test with Nix CI. The example below shows the workflow shape once your outputs exist.

Your repository needs a root flake.nix and committed flake.lock. Define packages, checks, and apps there; CI YAML selects those outputs. For example, .ngit/nix/workflows/ci.yaml can build two outputs concurrently and then run an app that consumes both:

yaml
version: 1
on:
  push:
    branches: [main]
jobs:
  test:
    build: checks.x86_64-linux.tests
    artifacts:
      report: report.json
  build:
    build: packages.x86_64-linux.my-app
    artifacts:
      binary: bin/my-app
  verify:
    needs: [test, build]
    run: apps.x86_64-linux.verify

These selectors and files must exist in your flake outputs. The tests check must run your tests and write report.json; selecting a package alone does not promise that tests run. The verify app receives the report at $NGIT_CI_INPUTS/test/artifacts/report/report.json and the binary at $NGIT_CI_INPUTS/build/artifacts/binary/bin/my-app.

Independent jobs may run on different machines with the same architecture. A needs job starts only after every predecessor has an accepted successful signed result. Provider slots control concurrency; YAML cannot pin a hostname. Ordinary pull-request jobs, including secret-free run apps for previews, require hardware-isolated providers. They receive no repository secrets and cannot write to the shared cache, even when the author is a maintainer. Keep production publication in an authorized push or manual workflow. See the Nix reference for artifact paths, app environments, secrets, and schema limits.

Keep dependencies between derivations inside Nix. A workflow needs edge is for handing signed results and artifacts to a later run app, not for rebuilding a package already represented in the Nix graph. The root flake can import a separate nix/ci.nix module while keeping one pinned input graph.

act: Actions-compatible steps ​

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. Review their triggers during migration to avoid running the same checks twice.

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

Publish an nsite preview

An act 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 a Nix run stays in progress ​

Workflow progress does not prove that a job has started executing. Ready Nix jobs can be waiting for a compatible provider with free capacity. Check the individual Job Results and ask the operator to compare provider advertisements with worker logs. If a worker advertises a free slot but receives no allocation, inspect discovery-relay connectivity and subscription limits. A capacity wait ends with a startup failure after its deadline; rerun after the operator fixes the cause. See provider recovery.

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 family and its act labels or Nix systems?
  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/nix/workflows/ci.yaml --json
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 ​

Act 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. For Nix jobs, only the declared, authorized values are encrypted to the selected trusted provider and exposed as files under NGIT_CI_SECRETS_DIR. Build jobs cannot receive secrets. See secret-bearing Nix work. 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 ​