Actions¶
Actions are the response-side counterpart of Triggers. Where a Trigger fires an evaluation when an event arrives, an Action reacts to evaluation and build lifecycle events and does something: sends an email, calls a webhook, or posts a commit status back to a forge.
Actions are per-project. Three types ship in v1:
| Type | Summary | Prerequisite |
|---|---|---|
send_mail |
Email one or more recipients | Server SMTP configured |
send_web_request |
HTTP POST to an external URL | None |
forge_status_report |
Post commit status to a forge | Outbound integration in the org |
Events¶
| Event | Fired when |
|---|---|
evaluation.queued |
Evaluation enters the queue |
evaluation.started |
Evaluation begins running |
evaluation.building |
Evaluation enters the building phase |
evaluation.completed |
Evaluation completed successfully |
evaluation.failed |
Evaluation failed |
evaluation.aborted |
Evaluation was aborted |
evaluation.action_required |
Evaluation parked waiting for maintainer approval on a fork PR |
evaluation.approval_granted |
Maintainer cleared the approval gate (flips the Approval check to success) |
build.queued |
Build enters the queue |
build.started |
Build starts executing on a worker |
build.completed |
Build completed successfully |
build.failed |
Build failed |
build.substituted |
Build output came from an upstream cache substitution |
An action with an empty events list never fires. forge_status_report ignores the events list — it is hard-wired to build.started, build.completed, and build.failed.
Send Mail¶
Uses the server-global SMTP configuration (services.gradient.email.*). If SMTP is not configured, creating a send_mail action returns 400.
Config fields:
| Field | Required | Description |
|---|---|---|
recipients |
yes | List of email addresses |
subject_template |
no | Subject line with placeholders |
Subject placeholders: {event}, {project}, {org}, {id}, {status}
Default subject: [Gradient] {event}: {project}
Default body includes: event name, project slug, entity id (eval/build UUID), status, and a link to the Gradient UI.
Send Web Request¶
POSTs a JSON payload to a URL. Optional Authorization: Bearer <token> header.
Config fields:
| Field | Required | Description |
|---|---|---|
url |
yes | HTTPS endpoint |
token |
no | Bearer token (write-only; never returned in reads) |
Request headers:
Content-Type: application/json
X-Gradient-Event: build.completed
Authorization: Bearer <token> # only if token is set
Payload shape:
{
"event": "build.completed",
"project": "my-project",
"organization": "acme",
"id": "<eval-or-build-uuid>",
"status": "completed"
}
Token management: the plaintext token is revealed exactly once — on create or after POST .../regenerate-token. Store it immediately.
Forge Status Report¶
Posts commit status (pending / success / failure / action-required) back to the forge as three separate check runs per PR — gradient/{project}: Approval (fork-PR gate), gradient/{project}: Evaluation (eval phase), and gradient/{project}: Build {label} (one per build, labelled by entry-point name or by {drv-name}.{architecture} when no entry-point matched). Each check is updated in place as the phase progresses; the Approval check flips to Success when a maintainer clears the gate, and the Evaluation check is posted as Pending at the same instant so the PR immediately reflects that the pipeline is in flight.
Config fields:
| Field | Required | Description |
|---|---|---|
integration_id |
yes | UUID of an outbound integration in the same org |
The integration must be kind: outbound. The forge type determines the API call format (Gitea, GitLab, GitHub App).
Declarative configuration via Nix¶
services.gradient.state.projects.web-app = {
actions = [
{
name = "notify-failures";
type = "send_mail";
events = [ "evaluation.failed" "build.failed" ];
config = {
recipients = [ "ops@example.com" ];
subject_template = "[Gradient] {event}: {project}";
};
}
{
name = "webhook-completed";
type = "send_web_request";
events = [ "build.completed" ];
config = {
url = "https://hooks.example.com/gradient";
token_file = "/run/credentials/gradient.service/webhook-token";
};
}
{
name = "github-status";
type = "forge_status_report";
config = {
integration = "github-main";
};
}
];
};
token_file is read at activation time and stored encrypted. It is not re-read on reload; rotate with services.gradient.state.projects.<name>.actions.<n>.config.token_file and systemctl restart gradient.
State-managed actions (managed: true) cannot be mutated through the API; remove or change them via NixOS config.
Troubleshooting¶
Open the action's Deliveries popup in the UI (Actions page → click the delivery count badge on any action row). Each row shows:
- HTTP status or error message
- Duration (ms)
- Request body sent
- Response body received (if any)
Common issues:
| Symptom | Cause |
|---|---|
400 on create (send_mail) |
SMTP not configured on the server |
Delivery shows connection refused |
Target URL unreachable from the server |
| No deliveries logged | Action active: false, or no matching events fired |
403 on regenerate-token |
Action is not of type send_web_request |