Webhook condition (filter_cel)
A provider webhook often fires on every event: a GitHub repository webhook
delivers pull-request opens, edits, comments, and review requests on the same
endpoint. Without a condition, each one starts a run and spends AI-query
quota before an instruction in the prompt gets a chance to ignore it. Set
filter_cel on the webhook so Aiden rejects the delivery before dispatch.
Use this when:
- One webhook endpoint receives several event types, but only one should start work (for example, a direct review request naming your bot)
- You want to try a condition against sample JSON and headers before saving it
- You want to stop paying for runs that an “ignore this” instruction would have discarded anyway
filter_cel is a CEL boolean expression
evaluated against body (the decoded JSON payload) and headers (sanitized,
lowercase provider headers — auth headers are never exposed). An empty or
omitted condition keeps existing behavior and accepts every delivery. A
condition that evaluates to false returns the webhook’s configured
acknowledgement with no run, no invocation row, and no AI-query charge.
Invalid conditions are rejected on save; evaluation errors at delivery time
fail visibly instead of silently admitting the event.
How to run
export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
pip install stackgen-sdk
python examples/automation/webhook-filter-cel.py --agent my-pr-review-agent --reviewer release-botexport STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
npm install stackgen-sdk tsx
npx tsx examples/automation/webhook-filter-cel.ts --agent my-pr-review-agent --reviewer release-botexport STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
go run ./examples/go/automation/webhook-filter-cel.go --agent my-pr-review-agent --reviewer release-botMinimal
from stackgen import StackgenClient, StackgenConfig
client = StackgenClient(
StackgenConfig(
base_url="https://app.stackgen.com",
api_token="stackgen_…",
workspace_id="<workspace-uuid>",
)
)
filter_cel = (
'("x-github-event" in headers) && '
'headers["x-github-event"] == "pull_request" && '
'has(body.action) && body.action == "review_requested" && '
"has(body.requested_reviewer) && body.requested_reviewer != null && "
"has(body.requested_reviewer.login) && "
'body.requested_reviewer.login.matches("(?i)^release-bot$")'
)
# Try it before saving anything.
matched = client.aiden.preview_webhook_filter(
filter_cel,
'{"action": "review_requested", "requested_reviewer": {"login": "release-bot"}}',
headers=[{"name": "X-GitHub-Event", "value": "pull_request"}],
)
print("matches a direct request for release-bot:", matched)
wh = client.aiden.create_webhook({
"name": "github-pr-review",
"target_type": "agent",
"target_name": "my-pr-review-agent",
"action": "Review this pull request and reply Done",
"enabled": True,
"filter_cel": filter_cel,
})
print(wh["id"], wh.get("filter_cel"), wh.get("token"))import { StackgenClient, StackgenConfig } from "stackgen-sdk";
const client = new StackgenClient(
new StackgenConfig({
baseUrl: "https://app.stackgen.com",
apiToken: "stackgen_…",
workspaceId: "<workspace-uuid>",
}),
);
const filterCel =
'("x-github-event" in headers) && ' +
'headers["x-github-event"] == "pull_request" && ' +
'has(body.action) && body.action == "review_requested" && ' +
"has(body.requested_reviewer) && body.requested_reviewer != null && " +
"has(body.requested_reviewer.login) && " +
'body.requested_reviewer.login.matches("(?i)^release-bot$")';
// Try it before saving anything.
const matched = await client.aiden.previewWebhookFilter(
filterCel,
'{"action": "review_requested", "requested_reviewer": {"login": "release-bot"}}',
{ headers: [{ name: "X-GitHub-Event", value: "pull_request" }] },
);
console.log("matches a direct request for release-bot:", matched);
const wh = await client.aiden.createWebhook({
name: "github-pr-review",
target_type: "agent",
target_name: "my-pr-review-agent",
action: "Review this pull request and reply Done",
enabled: true,
filter_cel: filterCel,
});
console.log(wh.id, wh.filter_cel, wh.token);filterCel := `("x-github-event" in headers) && ` +
`headers["x-github-event"] == "pull_request" && ` +
`has(body.action) && body.action == "review_requested" && ` +
`has(body.requested_reviewer) && body.requested_reviewer != null && ` +
`has(body.requested_reviewer.login) && ` +
`body.requested_reviewer.login.matches("(?i)^release-bot$")`
// Try it before saving anything.
matched, err := client.Aiden.PreviewWebhookFilter(ctx, filterCel,
`{"action":"review_requested","requested_reviewer":{"login":"release-bot"}}`,
[]map[string]stringname)
if err != nil { return err }
fmt.Println("matches a direct request for release-bot:", matched)
wh, err := client.Aiden.CreateWebhook(ctx, map[string]any{
"name": "github-pr-review",
"target_type": "agent",
"target_name": "my-pr-review-agent",
"action": "Review this pull request and reply Done",
"enabled": true,
"filter_cel": filterCel,
})
if err != nil { return err }
fmt.Println(wh["id"], wh["filter_cel"], wh["token"])Clear a condition
Send an explicit empty string on update to accept every event again. Omitting
filter_cel on update leaves the existing condition unchanged.
client.aiden.update_webhook(wh["id"], {"filter_cel": ""})await client.aiden.updateWebhook(wh.id as string, { filter_cel: "" });_, err = client.Aiden.UpdateWebhook(ctx, wh["id"].(string), map[string]any{"filter_cel": ""})Complete scripts
The full script creates the webhook, previews five samples (a human reviewer
match, a bot reviewer match, a different reviewer, a plain PR edit, and a
ping event), triggers a nonmatching PR-edited delivery and confirms no new
invocation was recorded, triggers a matching review-request delivery and
confirms one was, then clears the condition and deletes the webhook.
- Python:
examples/automation/webhook-filter-cel.py - TypeScript:
examples/automation/webhook-filter-cel.ts - Go:
examples/go/automation/webhook-filter-cel.go
#!/usr/bin/env python3
"""Filter a GitHub webhook so only your review requests start an agent.
A GitHub repository webhook delivers every pull-request event: opened, edited,
comments, team review requests, and direct review requests. Only a direct
request naming your reviewer should start the review agent — every other
delivery should just be acknowledged.
This sample:
1. Creates a webhook with a `filter_cel` condition that matches a
`review_requested` event naming one reviewer login (human or bot)
2. Previews the condition against a matching request, a different reviewer,
and a plain PR edit — without saving data or starting work
3. Triggers the webhook with a nonmatching PR-edited payload and confirms no
new invocation was recorded
4. Triggers it again with a matching review-request payload and confirms a
run was recorded
5. Clears the condition with update_webhook so every event is accepted again
Usage:
export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
python examples/automation/webhook-filter-cel.py --agent my-pr-review-agent
# Empty CI workspaces: prefer a published workflow (agents need an integration).
python examples/automation/webhook-filter-cel.py --workflow my-pr-review-workflow
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
import urllib.error
import urllib.request
import uuid
from stackgen import StackgenClient, StackgenConfig
def require_env(name: str, *fallbacks: str) -> str:
for key in (name, *fallbacks):
value = (os.environ.get(key) or "").strip()
if value:
return value
raise SystemExit(f"missing env {name}")
def build_filter_cel(reviewer_login: str) -> str:
"""Match a direct review request naming reviewer_login (human or bot), nothing else."""
return (
'("x-github-event" in headers) && '
'headers["x-github-event"] == "pull_request" && '
"has(body.action) && body.action == \"review_requested\" && "
"has(body.requested_reviewer) && body.requested_reviewer != null && "
"has(body.requested_reviewer.login) && "
f'body.requested_reviewer.login.matches("(?i)^{reviewer_login}$")'
)
def post_github_event(base_url: str, org_id: str, token: str, event: str, body: dict) -> int:
url = f"{base_url}/guild/api/v1/webhooks/trigger?orgId={org_id}"
req = urllib.request.Request(
url,
data=json.dumps(body).encode("utf-8"),
method="POST",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
"Content-Type": "application/json",
"X-GitHub-Event": event,
},
)
try:
with urllib.request.urlopen(req, timeout=60) as resp:
return resp.status
except urllib.error.HTTPError as err:
return err.code
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
target = parser.add_mutually_exclusive_group(required=True)
target.add_argument("--agent", help="Existing agent name")
target.add_argument("--workflow", help="Existing workflow name")
parser.add_argument(
"--reviewer",
default="release-bot",
help="GitHub login the filter accepts review requests for",
)
parser.add_argument(
"--message",
default="sdk: review this pull request and reply Done",
help="Webhook action prompt",
)
args = parser.parse_args()
target_type = "agent" if args.agent else "workflow"
target_name = args.agent or args.workflow
base_url = require_env("STACKGEN_URL").rstrip("/")
org_id = require_env("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT")
client = StackgenClient(
StackgenConfig(
base_url=base_url,
api_token=require_env("STACKGEN_TOKEN"),
workspace_id=org_id,
)
)
suffix = uuid.uuid4().hex[:8]
webhook_id = ""
filter_cel = build_filter_cel(args.reviewer)
pr_edited = {"action": "edited", "pull_request": {"number": 42}}
review_requested_match = {
"action": "review_requested",
"requested_reviewer": {"login": args.reviewer, "type": "User"},
}
review_requested_bot = {
"action": "review_requested",
"requested_reviewer": {"login": f"{args.reviewer}[bot]", "type": "Bot"},
}
review_requested_other = {
"action": "review_requested",
"requested_reviewer": {"login": "someone-else"},
}
try:
# 1. Preview the condition before saving anything.
print("--- previewing filter_cel (no webhook saved yet) ---")
for label, payload, event in [
("matching human reviewer", review_requested_match, "pull_request"),
("matching bot reviewer", review_requested_bot, "pull_request"),
("different reviewer", review_requested_other, "pull_request"),
("PR edited (no reviewer)", pr_edited, "pull_request"),
("ping event", {}, "ping"),
]:
matched = client.aiden.preview_webhook_filter(
filter_cel,
json.dumps(payload),
headers=[{"name": "X-GitHub-Event", "value": event}],
)
print(f" {label}: matched={matched}")
# 2. Create the webhook with the condition already attached.
wh = client.aiden.create_webhook(
{
"name": f"sdk-pr-review-filter-{suffix}",
"target_type": target_type,
"target_name": target_name,
"action": args.message,
"enabled": True,
"filter_cel": filter_cel,
}
)
webhook_id = str(wh.get("id") or "")
token = str(wh.get("token") or "")
print(f"\ncreated webhook id={webhook_id} filter_cel={wh.get('filter_cel')!r}")
if not token:
raise SystemExit("createWebhook did not return a token")
runs_before = len(client.aiden.list_webhook_runs(webhook_id))
# 3. A PR edit does not match — acknowledged, but no run recorded.
status = post_github_event(base_url, org_id, token, "pull_request", pr_edited)
print(f"PR edited delivery: ack status={status}")
time.sleep(2)
runs_after_edit = len(client.aiden.list_webhook_runs(webhook_id))
if runs_after_edit != runs_before:
raise SystemExit(
f"expected no new run for a nonmatching delivery "
f"(before={runs_before}, after={runs_after_edit})"
)
print(f"webhook runs after PR edit: {runs_after_edit} (unchanged)")
# 4. A direct review request for the reviewer matches — a run starts.
status = post_github_event(base_url, org_id, token, "pull_request", review_requested_match)
print(f"review_requested delivery: ack status={status}")
time.sleep(2)
runs_after_review = len(client.aiden.list_webhook_runs(webhook_id))
if runs_after_review <= runs_after_edit:
raise SystemExit(
f"expected a new run for the matching delivery "
f"(after_edit={runs_after_edit}, after_review={runs_after_review})"
)
print(f"webhook runs after review request: {runs_after_review} (started one run)")
# 5. Clear the condition — the webhook reverts to accepting every event.
cleared = client.aiden.update_webhook(webhook_id, {"filter_cel": ""})
print(f"cleared filter_cel={cleared.get('filter_cel')!r}")
finally:
if webhook_id:
try:
client.aiden.delete_webhook(webhook_id)
print(f"deleted webhook id={webhook_id}")
except Exception as cleanup_err: # noqa: BLE001 — best-effort teardown
print(f"cleanup delete_webhook failed: {cleanup_err}", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())/**
* Filter a GitHub webhook so only your review requests start an agent.
*
* A GitHub repository webhook delivers every pull-request event: opened, edited,
* comments, team review requests, and direct review requests. Only a direct
* request naming your reviewer should start the review agent — every other
* delivery should just be acknowledged.
*
* This sample:
*
* 1. Creates a webhook with a `filter_cel` condition that matches a
* `review_requested` event naming one reviewer login (human or bot)
* 2. Previews the condition against a matching request, a different reviewer,
* and a plain PR edit — without saving data or starting work
* 3. Triggers the webhook with a nonmatching PR-edited payload and confirms no
* new invocation was recorded
* 4. Triggers it again with a matching review-request payload and confirms a
* run was recorded
* 5. Clears the condition with updateWebhook so every event is accepted again
*
* Usage:
* export STACKGEN_URL=https://app.stackgen.com
* export STACKGEN_TOKEN=stackgen_…
* export STACKGEN_WORKSPACE_ID=<workspace-uuid>
* npx tsx examples/automation/webhook-filter-cel.ts --agent my-pr-review-agent
* # Empty CI workspaces: prefer a published workflow (agents need an integration).
* npx tsx examples/automation/webhook-filter-cel.ts --workflow my-pr-review-workflow
*/
import { parseArgs } from "node:util";
import { randomUUID } from "node:crypto";
import { setTimeout as sleep } from "node:timers/promises";
import { StackgenClient, StackgenConfig } from "stackgen-sdk";
function requireEnv(name: string, ...fallbacks: string[]): string {
for (const key of [name, ...fallbacks]) {
const value = (process.env[key] || "").trim();
if (value) {
return value;
}
}
throw new Error(`missing env ${name}`);
}
function buildFilterCel(reviewerLogin: string): string {
// Match a direct review request naming reviewerLogin (human or bot), nothing else.
return (
'("x-github-event" in headers) && ' +
'headers["x-github-event"] == "pull_request" && ' +
'has(body.action) && body.action == "review_requested" && ' +
"has(body.requested_reviewer) && body.requested_reviewer != null && " +
"has(body.requested_reviewer.login) && " +
`body.requested_reviewer.login.matches("(?i)^${reviewerLogin}$")`
);
}
async function postGithubEvent(
baseUrl: string,
workspaceId: string,
token: string,
event: string,
body: Record<string, unknown>,
): Promise<number> {
const url = `${baseUrl}/guild/api/v1/webhooks/trigger?orgId=${encodeURIComponent(workspaceId)}`;
const resp = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
"Content-Type": "application/json",
"X-GitHub-Event": event,
},
body: JSON.stringify(body),
});
return resp.status;
}
async function main(): Promise<void> {
const { values } = parseArgs({
options: {
agent: { type: "string" },
workflow: { type: "string" },
reviewer: { type: "string", default: "release-bot" },
message: {
type: "string",
default: "sdk: review this pull request and reply Done",
},
},
});
if (Boolean(values.agent) === Boolean(values.workflow)) {
throw new Error("require exactly one of --agent or --workflow");
}
const targetType = values.agent ? "agent" : "workflow";
const targetName = values.agent || values.workflow!;
const reviewer = values.reviewer!;
const baseUrl = requireEnv("STACKGEN_URL").replace(/\/$/, "");
const orgId = requireEnv("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT");
const client = new StackgenClient(
new StackgenConfig({
baseUrl,
apiToken: requireEnv("STACKGEN_TOKEN"),
workspaceId: orgId,
}),
);
const suffix = randomUUID().slice(0, 8);
let webhookId = "";
const filterCel = buildFilterCel(reviewer);
const prEdited = { action: "edited", pull_request: { number: 42 } };
const reviewRequestedMatch = {
action: "review_requested",
requested_reviewer: { login: reviewer, type: "User" },
};
const reviewRequestedBot = {
action: "review_requested",
requested_reviewer: { login: `${reviewer}[bot]`, type: "Bot" },
};
const reviewRequestedOther = {
action: "review_requested",
requested_reviewer: { login: "someone-else" },
};
try {
// 1. Preview the condition before saving anything.
console.log("--- previewing filter_cel (no webhook saved yet) ---");
const samples: [string, Record<string, unknown>, string][] = [
["matching human reviewer", reviewRequestedMatch, "pull_request"],
["matching bot reviewer", reviewRequestedBot, "pull_request"],
["different reviewer", reviewRequestedOther, "pull_request"],
["PR edited (no reviewer)", prEdited, "pull_request"],
["ping event", {}, "ping"],
];
for (const [label, payload, event] of samples) {
const matched = await client.aiden.previewWebhookFilter(filterCel, JSON.stringify(payload), {
headers: [{ name: "X-GitHub-Event", value: event }],
});
console.log(` ${label}: matched=${matched}`);
}
// 2. Create the webhook with the condition already attached.
const wh = await client.aiden.createWebhook({
name: `sdk-pr-review-filter-${suffix}`,
target_type: targetType,
target_name: targetName,
action: values.message,
enabled: true,
filter_cel: filterCel,
});
webhookId = String(wh.id ?? "");
const token = String(wh.token ?? "");
console.log(`\ncreated webhook id=${webhookId} filter_cel=${JSON.stringify(wh.filter_cel)}`);
if (!token) {
throw new Error("createWebhook did not return a token");
}
const runsBefore = (await client.aiden.listWebhookRuns(webhookId)).length;
// 3. A PR edit does not match — acknowledged, but no run recorded.
let status = await postGithubEvent(baseUrl, orgId, token, "pull_request", prEdited);
console.log(`PR edited delivery: ack status=${status}`);
await sleep(2000);
const runsAfterEdit = (await client.aiden.listWebhookRuns(webhookId)).length;
if (runsAfterEdit !== runsBefore) {
throw new Error(
`expected no new run for a nonmatching delivery (before=${runsBefore}, after=${runsAfterEdit})`,
);
}
console.log(`webhook runs after PR edit: ${runsAfterEdit} (unchanged)`);
// 4. A direct review request for the reviewer matches — a run starts.
status = await postGithubEvent(baseUrl, orgId, token, "pull_request", reviewRequestedMatch);
console.log(`review_requested delivery: ack status=${status}`);
await sleep(2000);
const runsAfterReview = (await client.aiden.listWebhookRuns(webhookId)).length;
if (runsAfterReview <= runsAfterEdit) {
throw new Error(
`expected a new run for the matching delivery (afterEdit=${runsAfterEdit}, afterReview=${runsAfterReview})`,
);
}
console.log(`webhook runs after review request: ${runsAfterReview} (started one run)`);
// 5. Clear the condition — the webhook reverts to accepting every event.
const cleared = await client.aiden.updateWebhook(webhookId, { filter_cel: "" });
console.log(`cleared filter_cel=${JSON.stringify(cleared.filter_cel)}`);
} finally {
if (webhookId) {
try {
await client.aiden.deleteWebhook(webhookId);
console.log(`deleted webhook id=${webhookId}`);
} catch (err) {
console.warn("cleanup deleteWebhook failed:", err);
}
}
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});// Filter a GitHub webhook so only your review requests start an agent.
//
// A GitHub repository webhook delivers every pull-request event: opened, edited,
// comments, team review requests, and direct review requests. Only a direct
// request naming your reviewer should start the review agent — every other
// delivery should just be acknowledged.
//
// Documented at https://appcd-dev.github.io/stackgen-sdk/code-samples/webhook-filter-cel/
//
// go run ./examples/go/automation/webhook-filter-cel.go --agent my-pr-review-agent
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"
stackgen "github.com/appcd-dev/stackgen-sdk/clients/go"
)
// envOr returns the first non-empty environment variable among keys.
func envOr(keys ...string) string {
for _, key := range keys {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
}
return ""
}
// buildFilterCel matches a direct review request naming reviewerLogin (human or bot), nothing else.
func buildFilterCel(reviewerLogin string) string {
return `("x-github-event" in headers) && ` +
`headers["x-github-event"] == "pull_request" && ` +
`has(body.action) && body.action == "review_requested" && ` +
`has(body.requested_reviewer) && body.requested_reviewer != null && ` +
`has(body.requested_reviewer.login) && ` +
fmt.Sprintf(`body.requested_reviewer.login.matches("(?i)^%s$")`, reviewerLogin)
}
func postGithubEvent(baseURL, orgID, token, event string, body map[string]any) (int, error) {
raw, err := json.Marshal(body)
if err != nil {
return 0, err
}
endpoint := strings.TrimRight(baseURL, "/") + "/guild/api/v1/webhooks/trigger?" +
url.Values{"orgId": {orgID}}.Encode()
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))
if err != nil {
return 0, err
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-GitHub-Event", event)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
return resp.StatusCode, nil
}
func main() {
agent := flag.String("agent", "", "Existing agent name")
workflow := flag.String("workflow", "", "Existing workflow name")
reviewer := flag.String("reviewer", "release-bot", "GitHub login the filter accepts review requests for")
message := flag.String("message", "sdk: review this pull request and reply Done", "Webhook action prompt")
flag.Parse()
if (*agent == "") == (*workflow == "") {
fmt.Fprintln(os.Stderr, "pass exactly one of --agent or --workflow")
os.Exit(2)
}
base, token, org := envOr("STACKGEN_URL"), envOr("STACKGEN_TOKEN"), envOr("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT")
if base == "" || token == "" || org == "" {
fmt.Fprintln(os.Stderr, "set STACKGEN_URL, STACKGEN_TOKEN, and STACKGEN_WORKSPACE_ID")
os.Exit(2)
}
targetType, targetName := "agent", *agent
if *workflow != "" {
targetType, targetName = "workflow", *workflow
}
client, err := stackgen.NewClient(stackgen.Config{BaseURL: base, APIToken: token, WorkspaceID: org})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
ctx := context.Background()
filterCel := buildFilterCel(*reviewer)
prEdited := map[string]any{"action": "edited", "pull_request": map[string]any{"number": 42}}
reviewRequestedMatch := map[string]any{
"action": "review_requested",
"requested_reviewer": map[string]any{"login": *reviewer, "type": "User"},
}
// 1. Preview the condition before saving anything.
fmt.Println("--- previewing filter_cel (no webhook saved yet) ---")
matched, err := client.Aiden.PreviewWebhookFilter(ctx, filterCel, mustJSON(reviewRequestedMatch),
[]map[string]string{{"name": "X-GitHub-Event", "value": "pull_request"}})
if err != nil {
fmt.Fprintln(os.Stderr, "preview error:", err)
os.Exit(1)
}
fmt.Printf(" matching human reviewer: matched=%v\n", matched)
matched, err = client.Aiden.PreviewWebhookFilter(ctx, filterCel, mustJSON(prEdited),
[]map[string]string{{"name": "X-GitHub-Event", "value": "pull_request"}})
if err != nil {
fmt.Fprintln(os.Stderr, "preview error:", err)
os.Exit(1)
}
fmt.Printf(" PR edited (no reviewer): matched=%v\n", matched)
// 2. Create the webhook with the condition already attached.
wh, err := client.Aiden.CreateWebhook(ctx, map[string]any{
"name": fmt.Sprintf("sdk-pr-review-filter-%d", time.Now().UnixNano()%1_000_000),
"target_type": targetType,
"target_name": targetName,
"action": *message,
"enabled": true,
"filter_cel": filterCel,
})
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
webhookID, _ := wh["id"].(string)
webhookToken, _ := wh["token"].(string)
fmt.Printf("\ncreated webhook id=%s filter_cel=%v\n", webhookID, wh["filter_cel"])
defer func() {
if webhookID != "" {
_, _ = client.Aiden.DeleteWebhook(context.Background(), webhookID)
fmt.Printf("deleted webhook id=%s\n", webhookID)
}
}()
if webhookToken == "" {
fmt.Fprintln(os.Stderr, "create webhook did not return a token")
os.Exit(1)
}
runsBefore := len(listRuns(ctx, client, webhookID))
// 3. A PR edit does not match — acknowledged, but no run recorded.
status, err := postGithubEvent(base, org, webhookToken, "pull_request", prEdited)
if err != nil {
fmt.Fprintln(os.Stderr, "trigger error:", err)
os.Exit(1)
}
fmt.Printf("PR edited delivery: ack status=%d\n", status)
time.Sleep(2 * time.Second)
runsAfterEdit := len(listRuns(ctx, client, webhookID))
if runsAfterEdit != runsBefore {
fmt.Fprintf(os.Stderr, "expected no new run for a nonmatching delivery (before=%d, after=%d)\n", runsBefore, runsAfterEdit)
os.Exit(1)
}
fmt.Printf("webhook runs after PR edit: %d (unchanged)\n", runsAfterEdit)
// 4. A direct review request for the reviewer matches — a run starts.
status, err = postGithubEvent(base, org, webhookToken, "pull_request", reviewRequestedMatch)
if err != nil {
fmt.Fprintln(os.Stderr, "trigger error:", err)
os.Exit(1)
}
fmt.Printf("review_requested delivery: ack status=%d\n", status)
time.Sleep(2 * time.Second)
runsAfterReview := len(listRuns(ctx, client, webhookID))
if runsAfterReview <= runsAfterEdit {
fmt.Fprintf(os.Stderr, "expected a new run for the matching delivery (afterEdit=%d, afterReview=%d)\n", runsAfterEdit, runsAfterReview)
os.Exit(1)
}
fmt.Printf("webhook runs after review request: %d (started one run)\n", runsAfterReview)
// 5. Clear the condition — the webhook reverts to accepting every event.
cleared, err := client.Aiden.UpdateWebhook(ctx, webhookID, map[string]any{"filter_cel": ""})
if err != nil {
fmt.Fprintln(os.Stderr, "update error:", err)
os.Exit(1)
}
fmt.Printf("cleared filter_cel=%v\n", cleared["filter_cel"])
}
func mustJSON(v map[string]any) string {
raw, err := json.Marshal(v)
if err != nil {
panic(err)
}
return string(raw)
}
func listRuns(ctx context.Context, client *stackgen.Client, webhookID string) []map[string]any {
runs, err := client.Aiden.ListWebhookRuns(ctx, webhookID)
if err != nil {
fmt.Fprintln(os.Stderr, "list runs error:", err)
os.Exit(1)
}
return runs
}