Skip to content

Triggers

A trigger step declares the events that start a graph. Add one and the graph runs on its own whenever a matching event arrives, instead of waiting for someone to run it. emit steps send events out; trigger steps bring them in.

This step starts the graph whenever a run in the project fails:

- id: on_failure
type: trigger
config: { event: run.failed }

When you publish the graph, its trigger steps register it as an event handler, the same wiring spool handler set does.

A trigger step’s transport says how events reach it:

  • bus (the default) subscribes to the project’s event bus. The graph runs whenever a matching event arrives: a finished run, a spool event emit, a forge delivery.
  • webhook gives the graph its own inbound HTTP endpoint. Publishing mints the URL and a secret, and a POST to that URL starts the graph.

A bus trigger names the event pattern that starts the run in event:, as in the example above. It takes the same patterns as a handler’s --on: an exact type, a prefix wildcard such as run.*, or * for every event (Matching has the rules).

run.* also matches a run stopping for an approval, so name run.succeeded, run.failed or run.cancelled when you mean a run finishing. The event catalog lists every lifecycle type.

Deliveries to a webhook you created are bus events too. A forge’s are typed <webhook>.<forge event>[.<action>] (see Forge deliveries), so a bus trigger on github.pull_request.* runs the graph for every pull request action on that webhook.

A webhook trigger owns an endpoint the graph provisions on publish:

- id: on_pr
type: trigger
config:
transport: webhook
manifest: github
event: pull_request.*

Use one when this graph is the whole reaction to one sender and its endpoint should live and die with the graph: a form submission, a device checking in, a vendor callback, an alert from something that only speaks webhooks.

For a repo, use spool repo connect instead:

Terminal window
spool repo connect https://github.com/me/app --repo-credential gh-read

That keeps one webhook per forge however many graphs or pipelines react to it, and builds the pipeline or graph the pushed commit shipped with. With a trigger step per graph, ten graphs on one repo mean ten forge webhooks to create and later delete. CI from a repo covers the repo path.

The step’s config:

  • manifest (optional) selects signed-delivery verification and payload shaping for a known sender. github verifies the X-Hub-Signature-256 signature, works for GitHub, Gitea and Forgejo, and lifts fields like sha, ref and repo onto the event. Leave it off for a plain shared-secret webhook.
  • event (optional, manifest only) picks which deliveries start the run. It is matched against the delivery’s event name plus its action, the way a forge delivery’s type is built: push, pull_request.opened, or pull_request.* for every pull request action. A bare pull_request never fires, because pull request deliveries always carry an action. Omit event to run on every delivery, including the forge’s ping.

The endpoint is an ordinary webhook named graph.<graph>.<step>. A token trigger emits that name as its type; a manifest trigger emits graph.<graph>.<step>.<event>[.<action>], for example graph.ci.on_pr.pull_request.opened.

Renaming the trigger step re-mints the endpoint. Publishing after a rename drops graph.<graph>.<old-id> and creates graph.<graph>.<new-id> with a new URL and secret, so the sender’s posts go nowhere. Re-register the sender, or leave a live trigger step’s id alone.

When you publish, the trigger step’s inspector shows the ingest URL, the secret (once; a re-publish keeps it), and a Register on forge control that creates the webhook on a GitHub, Gitea or Forgejo repo given a forge admin credential. spool workflow set <name> <file> --publish prints the secret too. You can also paste the URL and secret into the repo’s webhook settings by hand; spool webhook get graph.<graph>.<step> shows the URL.

To replace the secret, rotate it under Settings > Connections > Webhooks or with spool webhook rotate graph.<graph>.<step>. For a manifest trigger that mints a new signing key, which you then give the forge.

A published webhook trigger's inspector: its transport, the github manifest and the push subtype, the ingest URL the graph owns, and Register on forge.

The trigger step’s output is the event that started the run, and the graph also receives it as its run input, so downstream steps can read either nodes.<trigger-id>.output or input. A run.failed bus trigger reads input.payload.error, input.payload.run_id and the rest of the lifecycle payload.

A webhook delivery is in input.payload, as {"body": …, "query": …}. A POST of {"sku": "TENT-2P", "qty": 12} to …/ingest?source=shop gives:

{ "body": { "sku": "TENT-2P", "qty": 12 }, "query": { "source": "shop" } }

Read it as input.payload.body.sku and input.payload.query.source. What a delivery looks like covers how body and query values are typed, and has a worked curl for a token trigger.

A manifest trigger’s delivery has the same payload, with the forge’s JSON at input.payload.body (input.payload.body.action, input.payload.body.pull_request.number). The shaped fields sit at the top: input.sha, input.ref, input.head_ref, input.base_ref, input.number, input.repo, input.sender, input.paths.

version: hyperspool-graph/v1
nodes:
- id: on_pr
type: trigger
config: { transport: webhook, manifest: github, event: pull_request.* }
- id: say
type: code
config:
script: echo "PR ${ACTION} on ${REPO} at ${SHA}"
env:
ACTION: { $expr: "input.payload.body.action" }
REPO: { $expr: "input.repo" }
SHA: { $expr: "input.sha" }
edges:
- { from: on_pr, to: say }

A pull request opened on acme/web prints PR opened on acme/web at <head sha>.

When you publish a graph, from the canvas or with spool workflow set <name> <file> --publish, its trigger steps become one event handler named after the graph. Its on list combines every trigger step’s pattern, so a graph with bus triggers on run.failed and deploy-done registers the same thing as:

Terminal window
spool handler set <graph> -w <graph> --on run.failed,deploy-done

A webhook trigger provisions its endpoint at the same moment and adds the endpoint’s event to that list, so there is nothing else to set up. spool handler list shows the result with a graph-trigger tag.

Remove every trigger step and republish, and the handler and any webhooks the graph owns are dropped; the graph goes back to being one you run by hand or spawn. Deleting the graph drops them too.

If a handler you made yourself already has the graph’s name, publishing the graph’s trigger steps would replace it, and only a project admin can do that; anyone else’s publish is refused. Give a hand-made handler for this graph a different name and publishing leaves it alone.

If a delivery didn’t start the graph, the webhook delivery log shows the type each delivery became and whether any handler matched it.

A notify graph wires one trigger step to one connector step. The trigger starts the graph on a failed run; the connector posts the error to Matrix:

version: hyperspool-graph/v1
nodes:
- id: on_failure
type: trigger
config: { event: run.failed }
- id: post
type: matrix.send_message
config:
credential: matrix
with:
homeserver: https://matrix.example
room_id: "!abc:example"
input:
text: { $template: "run failed: {{ input.payload.error }}" }
edges:
- { from: on_failure, to: post }

Publish it and a run.failed on the project bus posts to the room. That covers runs you submit, schedules, and runs started with spool spawn, including builds from spool repo connect. Some runs never announce their ending, such as one another handler started; Runs that don’t announce lists them.