You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: async execution with 202 response and executions query API
- Rule files can opt into async: true to return HTTP 202 immediately
and run matched actions in a background goroutine
- Global backpressure via server.max_async_tasks (default 32); excess
requests are rejected synchronously with 429
- Execution policies (block/cooldown) are still enforced at accept
time; the running mark is released when the background task ends
- New internal/execstore ring buffer keeps the last 100 async records,
queryable via GET /api/executions?limit=N
- Graceful shutdown waits up to 30s for in-flight async tasks before
closing rule loggers; shutdown order adjusted accordingly
- Docs updated: configuration/usage/README in both languages,
example.yaml gains an async comment
When `async: true`, a matched request is accepted immediately with HTTP 202 while the actions run in the background:
291
+
292
+
```yaml
293
+
name: "deploy"
294
+
async: true # return 202, execute in background
295
+
execution:
296
+
policy: "block"
297
+
rules:
298
+
- name: "deploy-main"
299
+
actions:
300
+
- type: "command"
301
+
cmd: "deploy.sh"
302
+
```
303
+
304
+
Behavior details:
305
+
306
+
- The 202 response carries a `request_id` used to correlate logs and execution records
307
+
- Execution policies (`block` / `cooldown`) are still enforced — policy rejections (409/429) are returned synchronously
308
+
- Backpressure: when the number of running background tasks reaches `server.max_async_tasks`, new requests are rejected with 429
309
+
- Recent async executions are queryable via `GET /api/executions` (see Usage docs)
310
+
- On shutdown, in-flight async tasks get a 30-second grace period before forced exit
311
+
286
312
### 2.3 `filters` — File-Level Filters
287
313
288
314
File-level filters act as **global constraints** applied to ALL rules in the config. They use **AND** logic with rule-level filters: both must match for a rule to execute.
Lists recent asynchronous executions (newest first), kept in memory (last 100). No authentication, consistent with `/health`. `limit` defaults to 20 and is capped at 100.
313
+
314
+
```bash
315
+
curl http://localhost:9000/api/executions?limit=5
316
+
```
317
+
318
+
```json
319
+
{
320
+
"executions": [
321
+
{
322
+
"request_id": "1720000000000-a1b2c3d4",
323
+
"config": "deploy",
324
+
"rule": "deploy-main",
325
+
"status": "succeeded",
326
+
"started_at": "2026-08-05T10:00:00Z",
327
+
"finished_at": "2026-08-05T10:00:12Z",
328
+
"duration": "12.05s",
329
+
"exit_code": 0
330
+
}
331
+
]
332
+
}
333
+
```
334
+
335
+
`status` is one of `running`, `succeeded`, or `failed`. Failed records carry `exit_code` (`-1` when not applicable) and an `error` message.
336
+
306
337
### Relay API
307
338
308
339
#### `GET /api/relay/status` — Comprehensive Relay Status (always available, no auth)
@@ -432,6 +463,22 @@ When `policy: "cooldown"` and the cooldown window is active:
432
463
}
433
464
```
434
465
466
+
### Accepted Asynchronously (202)
467
+
468
+
When the matched config has `async: true`, the request is accepted immediately and actions run in the background:
469
+
470
+
```json
471
+
{
472
+
"code": 202,
473
+
"message": "Accepted, executing asynchronously",
474
+
"config": "deploy",
475
+
"rule": "deploy-main",
476
+
"request_id": "1720000000000-a1b2c3d4"
477
+
}
478
+
```
479
+
480
+
Use `request_id` to correlate log entries and execution records.
481
+
435
482
### Base Route Disabled (400)
436
483
437
484
When `allow_all: false` and a request is sent to `/webhook`:
0 commit comments