spade publish

The spade publish command submits the current block collection to the cloud registry, which screens the source, builds the artifact, signs it, and makes it available to workers.

It does not upload an artifact. It submits a reference — (repo_url, commit_sha, subdir, collection, version, language) — for a commit you have already pushed. The registry clones your repository at exactly that commit and does the rest.

That indirection is the point. The registry screens source and then builds from the same commit SHA, so the artifact it signs is provably the result of building the exact code that was screened. Publishing local, unpushed changes would let the screened source and the deployed artifact diverge, which would defeat screening entirely.

Usage🔗

cd my-collection
spade publish                                   # private: only you can see, use, or install it
spade publish --public                          # anyone can discover, use, and install it
spade publish --clone-secret github-ro          # the registry clones a private repository with this secret
spade publish --clone-secret gl --clone-username deploy-token

No positional arguments. The collection is determined by your working directory, so a monorepo holding several collections publishes the right one based on where you run the command.

Requires a spade login session.

Flags🔗

FlagMeaning
--publicMake the collection public, so anyone can discover, use, and install it. Collections are private by default. On a later publish the flag is optional: omitting it leaves visibility as it is, and it never makes a public collection private again — that is done from the web UI's Collections page.
--clone-secret <name>The name of a cloud secret (stored with spade secret set --remote) holding a read-only token the registry uses to clone a private repository. The binding is remembered on the collection, so the flag is needed once, or whenever it changes.
--clone-username <user>The username presented with the clone secret. The default, x-access-token, is what GitHub expects for a personal access token; GitLab deploy tokens carry their own username. Needs --clone-secret.

See Visibility and sharing for what private and public mean.

What it does🔗

1. Validate the collection🔗

Runs the same manifest and structural checks as spade check. Errors are printed and nothing is submitted.

2. Refuse to publish what the registry cannot reproduce🔗

  • You must be inside a git repository.
  • The working tree must be clean — no uncommitted changes.
  • HEAD must be reachable on the configured remote — no unpushed commits.

Each of these fails locally, before anything is sent.

3. Resolve the reference🔗

FieldSource
collection, versionthe language's own manifest (Cargo.toml, go.mod, pyproject.toml, package.json, DESCRIPTION)
commit_shagit rev-parse HEAD
subdirgit rev-parse --show-prefix — empty when the repository root is the collection
repo_urlthe current branch's remote (origin by default), normalized

An SSH remote (git@github.com:org/repo.git) is rewritten to its https:// equivalent, because the registry's builder containers have git but no ssh client. The rewritten URL is printed so you can see what was submitted:

Publishing from https://github.com/org/repo (remote "origin" is git@github.com:org/repo.git)

A remote using a transport the registry cannot clone fails here rather than in the build queue.

4. Submit and track🔗

The reference is POSTed to the registry with your login session. The status endpoint is a bearer-authenticated JSON API rather than a page a browser can open, so spade publish tracks progress in the terminal instead of handing you a URL you cannot authenticate to:

Collection gdal is private: only you can see, use, or install it.
    Share it with named users from the web UI's Collections page, or publish with --public.
Submitted gdal@1.2.0. Tracking progress (Ctrl-C to stop):
  • submitted
  • screening source
  • screening passed
  • building artifact
  • available

gdal@1.2.0 is published and available.
    linux/amd64  sha256:a1b2c3d4e5f6…
    linux/arm64  sha256:9876fedcba01…

The visibility line is printed first, so a collection you meant to be public is not discovered private by a colleague who cannot see it.

Polling stops at a terminal state, or after 20 minutes with a note that the build continues on the registry. Ctrl-C stops watching, not the build. A failed publish exits non-zero and prints which screener rejected it, or why the clone failed:

Publish failed for gdal@1.2.0.
    screener source-scan: disallowed syscall in src/lib.rs

Publishing from a private repository🔗

The registry clones your repository itself, so a private repository needs a credential it can use. That credential is a cloud secret bound to the collection: the registry never stores the token, only the secret's name, and resolves the value under your account at build time.

  1. Create a read-only token for the repository. On GitHub, a fine-grained personal access token with Contents: read scoped to just the collection's repository; on GitLab, a deploy token with read_repository.

  2. Store it as a cloud secret, from the terminal or on the web UI's Secrets page:

    spade secret set --remote github-ro
    Enter secret value: ················
  3. Publish with the secret's name. The binding is remembered, so later publishes of the same collection need no flag:

    spade publish --clone-secret github-ro

    For a host that expects a specific username with the token, add --clone-username.

The CLI cannot tell a private repository from a missing one — asked anonymously, they look the same — so a publish from a private repository with no secret bound fails at the registry's clone step, with a message that names the flag:

Publish failed for gdal@1.2.0.
    cloning https://github.com/org/gdal: authentication failed; for a private
    repository bind a read-only token with `spade publish --clone-secret <name>`

Changing the collection's owner clears the binding, since the secret belonged to the previous owner's account; the new owner binds one of their own.

Version lifecycle🔗

A published version moves through these states:

StateMeaning
submittedRequest received, waiting in the screening queue
screeningScreening is running against the cloned source
screenedScreening passed; awaiting build
buildingThe registry is building the artifact in the bundler image
availableBuilt, signed, and stored — workers may install and execute it
failedScreening, build, or signing failed. Terminal

Three more are reached by owner or operator action rather than by the pipeline: deprecated (hidden from browsing, still installable), yanked (blocks new installs), and recalled (the operator-only hard security switch, reachable from any state and irreversible).

What publishing enables🔗

A published version is what workers install: they fetch the signed artifact by reference and verify it before executing. That is how a cloud run gets its blocks. The same artifact is what spade install <collection>@<version> fetches for a laptop — for a public collection, anyone's; for a private one, only the owner's.

Configuration🔗

The registry is addressed as <server>/registry, derived from the server URL stored by spade login. Set SPADE_REGISTRY_URL to point at a different registry.

See also🔗

  • spade check for running the same validation independently
  • spade install for building and registering a collection locally
  • spade login for authenticating, which publishing requires