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
Copy file name to clipboardExpand all lines: docs/configuration.md
+62-7Lines changed: 62 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -291,17 +291,46 @@ If neither `$global` nor the repo defines a `labels` key, the feature is a no-op
291
291
292
292
## Trigger
293
293
294
-
By default Layne scans every pull request immediately when it is opened, synchronised, or reopened. The `trigger` block lets you defer scanning until a specific CI workflow completes — useful for open-source repositories where workflows from external contributors require maintainer approval before they run.
294
+
By default Layne scans every pull request immediately when it is opened, synchronised, or reopened (`pull_request` trigger). This is the right choice for private or internal repositories where all contributors are trusted and every PR is worth scanning.
295
+
296
+
For public repositories, two problems arise:
297
+
298
+
**1. GitHub workflow approval gates.** GitHub requires maintainer approval before running Actions workflows for first-time external contributors. This means Layne's `pull_request` event fires and the scan starts running — spawning Semgrep processes, Trufflehog processes, and Anthropic API calls — on code that may never actually execute in CI because a maintainer hasn't approved it yet. You end up scanning throwaway spam PRs, bot noise, and low-effort contributions that will be closed without review.
299
+
300
+
**2. Wasted spend on failing code.** Even for trusted contributors, a PR that immediately breaks CI is unlikely to be merged. Scanning it early means burning Semgrep CPU time, Trufflehog I/O, and — most importantly — Anthropic API credits on code that will need to be revised anyway. If CI runs for 5 minutes and fails on a type error, the security scan result is moot.
301
+
302
+
The `workflow_run` and `workflow_job` triggers solve both problems by deferring the scan until after CI has already run. You only scan code that cleared your quality gate, which is almost always the only code that will ever land in your main branch.
303
+
304
+
### Cost impact
305
+
306
+
The Claude adapter makes Anthropic API calls charged per token. On a busy public repository, the difference between scanning every PR immediately and scanning only after CI passes can be significant:
307
+
308
+
- Repositories with high external contributor volume often receive many low-quality PRs (spam, trivial fixes, automated dependency bumps that fail tests). These will never merge and don't need security scanning.
309
+
- PRs that fail CI within the first few minutes consume scan compute for a result no one will act on. A 30-second CI failure gate that rejects 40% of PRs saves 40% of scan costs immediately.
310
+
- Semgrep and Trufflehog are cheap (CPU only), but the Claude adapter is billed per token at Anthropic API rates. On a repo with many PRs per day, this adds up quickly. Deferring to after CI passes is the single most effective cost control available.
311
+
312
+
The deferred triggers do not reduce security coverage for PRs that pass CI — the scan still runs on every commit that clears the gate, before merge.
313
+
314
+
### Choosing between `workflow_run` and `workflow_job`
315
+
316
+
||`workflow_run`|`workflow_job`|
317
+
|---|---|---|
318
+
| Gates on | An entire workflow completing | A single named job completing |
319
+
| Use when | You want CI fully done before scanning | You have a fast early gate (e.g. lint, approval job) and want to scan sooner |
320
+
| Latency | Scan starts after the longest job in the workflow | Scan starts as soon as the named job finishes |
321
+
| Typical setup | One CI workflow, wait for all of it | A dedicated `security-gate` job that runs approval checks early |
295
322
296
323
### Modes
297
324
298
325
|`on`| Behaviour |
299
326
|---|---|
300
327
|`pull_request`|*(default)* Scan fires immediately on `opened`, `synchronize`, and `reopened` events |
301
328
|`workflow_run`| Scan fires when the named CI workflow completes with a matching conclusion |
329
+
|`workflow_job`| Scan fires when the named CI job completes with a matching conclusion |
302
330
303
331
### Schema
304
332
333
+
**`workflow_run`:**
305
334
```json
306
335
{
307
336
"owner/repo": {
@@ -314,24 +343,38 @@ By default Layne scans every pull request immediately when it is opened, synchro
314
343
}
315
344
```
316
345
346
+
**`workflow_job`:**
347
+
```json
348
+
{
349
+
"owner/repo": {
350
+
"trigger": {
351
+
"on": "workflow_job",
352
+
"job": "security-gate",
353
+
"conclusions": ["success"]
354
+
}
355
+
}
356
+
}
357
+
```
358
+
317
359
| Key | Type | Default | Description |
318
360
|---|---|---|---|
319
-
|`on`|`"pull_request"`\|`"workflow_run"`|`"pull_request"`| When to trigger the scan |
361
+
|`on`|`"pull_request"`\|`"workflow_run"`\|`"workflow_job"`|`"pull_request"`| When to trigger the scan |
320
362
|`workflow`| string | — | Name of the GitHub Actions workflow to watch. Required when `on` is `"workflow_run"`|
321
-
|`conclusions`| string[]|`["success"]`| Workflow conclusions that trigger the scan. Valid values: `success`, `failure`, `neutral`, `cancelled`, `skipped`, `timed_out`, `action_required`|
363
+
|`job`| string | — | Name of the GitHub Actions job to watch. Required when `on` is `"workflow_job"`|
364
+
|`conclusions`| string[]|`["success"]`| Conclusions that trigger the scan. Valid values: `success`, `failure`, `neutral`, `cancelled`, `skipped`, `timed_out`, `action_required`|
322
365
323
366
> **`trigger` can be set globally.** Set it under `$global` to apply to all repos, then override per-repo as needed.
324
367
325
-
### How `workflow_run` works
368
+
### How deferred triggers work
326
369
327
-
When `on: workflow_run`is configured for a repo:
370
+
Both `workflow_run`and `workflow_job` follow the same two-stage pattern:
328
371
329
372
1.**On `pull_request`** — Layne caches the PR metadata in Redis (7-day TTL) and creates a `skipped` Check Run so the deferral is visible in the PR status UI. No scan is enqueued yet.
330
-
2.**On `workflow_run completed`** — When the named workflow finishes with a matching conclusion, Layne looks up the cached PR metadata and enqueues the scan. If the cache is cold (e.g. Layne was offline when the PR was opened), Layne falls back to the GitHub API to find the associated PR.
373
+
2.**On the trigger event completing** — When the named workflow or job finishes with a matching conclusion, Layne looks up the cached PR metadata and enqueues the scan. If the cache is cold (e.g. Layne was offline when the PR was opened), Layne falls back to the GitHub API to find the associated PR.
331
374
332
375
### Failure mode
333
376
334
-
If the watched workflow is renamed or removed, Layne never receives the`workflow_run` event and the scan never runs. To fail **closed** (safe) rather than **open** (silent), make Layne's Check Run a **required status check** in branch protection — then a missing check blocks merging and the absence is immediately visible.
377
+
If the watched workflow or job is renamed or removed, Layne never receives the event and the scan never runs. To fail **closed** (safe) rather than **open** (silent), make Layne's Check Run a **required status check** in branch protection — then a missing check blocks merging and the absence is immediately visible.
335
378
336
379
### Examples
337
380
@@ -347,6 +390,18 @@ If the watched workflow is renamed or removed, Layne never receives the `workflo
347
390
}
348
391
```
349
392
393
+
**Scan after a specific job completes (finer-grained than a whole workflow):**
394
+
```json
395
+
{
396
+
"owner/repo": {
397
+
"trigger": {
398
+
"on": "workflow_job",
399
+
"job": "security-gate"
400
+
}
401
+
}
402
+
}
403
+
```
404
+
350
405
**Scan regardless of whether CI passes or fails (workflow was approved, code is worth scanning):**
0 commit comments