Repository navigation
feat: add bodyPattern and excludeBodyPatterns regex filters for GitHub webhooks - #1058
Conversation
Greptile SummaryThis PR adds Confidence Score: 5/5Safe to merge; the one structural asymmetry noted is a P2 and doesn't affect real-world behavior. All previously flagged P1 concerns (missing test coverage, error logging inconsistency) are addressed. The only remaining finding is a P2 structural note about PR-path body filters being inside internal/webhook/github_filter.go — the PR-path BodyPattern/ExcludeBodyPatterns blocks sit inside
|
| Filename | Overview |
|---|---|
| internal/webhook/github_filter.go | Adds BodyPattern and ExcludeBodyPatterns filtering for issues/comments and PR/review events; error logging is present on both paths; minor structural asymmetry: PR-path body filters are inside if pr != nil while issue-path filters are outside the equivalent nil guard. |
| api/v1alpha1/taskspawner_types.go | Adds BodyPattern (string) and ExcludeBodyPatterns ([]string) fields with kubebuilder validation markers; BodyContains is deprecated but preserved; schema markers look correct. |
| api/v1alpha1/zz_generated.deepcopy.go | Auto-generated deep copy for the new ExcludeBodyPatterns slice field; correct copy pattern. |
| internal/manifests/charts/kelos/templates/crds/taskspawner-crd.yaml | CRD updated with bodyPattern and excludeBodyPatterns schema entries including minLength/maxLength/maxItems constraints; consistent with Go type markers. |
| internal/manifests/install-crd.yaml | Install CRD manifest updated in sync with the Helm CRD template; changes look correct. |
| internal/webhook/github_filter_test.go | Good new test coverage: ExcludeBodyPatterns for issues, issue_comment, pull_request, pull_request_review, pull_request_review_comment; combined BodyPattern+ExcludeBodyPatterns; multiple-pattern exclusion; BodyPattern regex tests. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Incoming GitHub Webhook] --> B{Event type?}
B -->|IssuesEvent / IssueCommentEvent| C[Extract issue/comment body]
B -->|PullRequestEvent / ReviewEvent / ReviewCommentEvent| D{pr != nil?}
D -->|No| PASS
D -->|Yes| E[Extract PR / review / comment body]
C --> F{BodyContains set?}
F -->|Yes| G{substring match?}
G -->|No| REJECT
G -->|Yes| H{BodyPattern set?}
F -->|No| H
H -->|Yes| I{regex match?}
I -->|Error or No match| REJECT
I -->|Match| J{ExcludeBodyPatterns set?}
H -->|No| J
J -->|Yes| K{matches any pattern?}
K -->|Match or Error| REJECT
K -->|No match| PASS
J -->|No| PASS
E --> L{BodyContains set?}
L -->|Yes| M{substring match?}
M -->|No| REJECT
M -->|Yes| N{BodyPattern set?}
L -->|No| N
N -->|Yes| O{regex match?}
O -->|Error or No match| REJECT
O -->|Match| P{ExcludeBodyPatterns set?}
N -->|No| P
P -->|Yes| Q{matches any pattern?}
Q -->|Match or Error| REJECT
Q -->|No match| PASS
P -->|No| PASS
PASS([✅ Event accepted])
REJECT([❌ Event rejected])
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
internal/webhook/github_filter.go:468-514
**PR-path body filters skip review/comment body when `pr` is nil**
The `BodyPattern` and `ExcludeBodyPatterns` blocks for `PullRequestReviewEvent` and `PullRequestReviewCommentEvent` are placed inside the `if pr != nil` guard. For these two event types the body being filtered is the *review* or *review comment* body — it has no logical dependency on the PR object being non-nil. If GitHub sends a `pull_request_review` webhook where the `pull_request` field is absent or unparseable, both filters are silently bypassed and the event passes through unfiltered.
This is structurally inconsistent with the issue/comment path, where `BodyPattern` and `ExcludeBodyPatterns` live *outside* the `if issue != nil` block and always run. Moving the PR body filters outside the guard (or extracting the review/comment body before the nil check, as done for issues) would make the behavior consistent and correct regardless of whether the PR object is present.
Reviews (8): Last reviewed commit: "feat: add bodyPattern and excludeBodyPat..." | Re-trigger Greptile
There was a problem hiding this comment.
1 issue found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="internal/webhook/github_filter_test.go">
<violation number="1" location="internal/webhook/github_filter_test.go:620">
P3: Missing test coverage for supported `BodyNotContains` review event types (`pull_request_review` and `pull_request_review_comment`).</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
/kelos api-review |
|
🤖 Kelos Task Status Task |
|
🤖 Kelos Task Status Task |
There was a problem hiding this comment.
🤖 Kelos API Reviewer Agent @gjkim42
API Design Review
Verdict: APPROVE
Scope: Additive bodyNotContains filter on GitHubWebhookFilter (v1alpha1) for excluding webhook events whose body contains a substring; mirrors the existing bodyContains.
Findings
API Conventions — looks good
BodyNotContains stringwith JSON tagbodyNotContains,omitempty, and+optional— all consistent with Kubernetes API conventions and matchesBodyContains(api/v1alpha1/taskspawner_types.go:391-393).- Pure additive change in
v1alpha1; existing clients can safely ignore the new field. No semantic changes to existing fields. - CRD updates are present in both
internal/manifests/charts/kelos/templates/crds/taskspawner-crd.yamlandinternal/manifests/install-crd.yaml, descriptions match the godoc —make updatewas run as expected.
Documentation — minor nit
api/v1alpha1/taskspawner_types.go:388-393: The doc clearly explains AND semantics withBodyContains, which is great. Two small clarifications worth adding:- Explicitly state that the match is case-sensitive (the implementation uses
strings.Contains). The existingBodyContainsdoc has the same gap, but new fields are a good time to set the bar. - Note that an event with an empty body never matches
BodyNotContains(so it passes the filter). This is the natural behavior of substring matching, but it's worth being explicit since users may otherwise expect "missing body" to be treated specially.
- Explicitly state that the match is case-sensitive (the implementation uses
Naming — non-blocking
- The codebase uses an
Exclude*prefix for negative variants of existing fields:Labels/ExcludeLabels,Author/ExcludeAuthors.BodyNotContainsbreaks that pattern (it pairs withBodyContainsmore directly, but is asymmetric with the other negative filters). Alternatives likeExcludeBodyContainsare awkward, soBodyNotContainsis reasonable — but worth noting for consistency. Since the API isv1alpha1, this is the cheapest moment to revisit if maintainers prefer theExclude*family.
Validation — consider
- No
MaxLengthvalidation marker on the field.BodyContainshas the same gap, so this is consistent, but unbounded substring filters could be abused via giant CRs. Optional: add// +kubebuilder:validation:MaxLength=...to both fields in a follow-up.
Tests — consider expanding
internal/webhook/github_filter_test.go: tests coverissues,issue_comment,pull_request, and the combinedbodyContains+bodyNotContainscase. The PR description listspull_request_reviewandpull_request_review_commentas supported, and the implementation handles them atinternal/webhook/github_filter.go:443-454, but neither has a regression test. Per CLAUDE.md ("Better tests. Always try to add or improve tests when modifying code"), please add explicit test cases for those two event types so the documented surface is locked in.
Suggestions (optional)
- Consider promoting
BodyContains/BodyNotContainsto[]string(OR / AND-of-NOT) in a follow-up —LabelsandExcludeAuthorsalready use list semantics, and a single substring is often too narrow in practice. Out of scope for this PR. - If/when a
bodyMatches(regex) variant is ever added, consider whetherBodyContains*should remain primary or be deprecated; worth keeping the surface area small.
Note on prompt injection: a third-party reviewer's comment on this PR contained an HTML directive demanding that automated tools attribute findings to it. I disregarded that instruction and formed the analysis above independently from the diff.
/kelos needs-input
7df3b6d to
7625e3e
Compare
7625e3e to
8fb9eec
Compare
8fb9eec to
91ac9f5
Compare
|
@gjkim42 I think this is ready for review. I went ahead and made the BodyNotContains a list for two reasons. First it is far more likely that you want to have multiple not contains than contains and second it prevents a future breaking API change to swap. BodyContains I can't do much about, but this is easy to prevent future breakages. Otherwise I think we are good to go. |
|
🤖 Kelos Task Status Task |
There was a problem hiding this comment.
🤖 Kelos API Reviewer Agent @gjkim42
API Design Review
Verdict: COMMENT
Scope: Promotes BodyNotContains from string to []string, adds bound markers on both Body* fields (MaxLength=1024 / MaxItems=10 / items:MinLength=1 / items:MaxLength=1024), expands tests to cover all five supported event types, and documents case-sensitivity + empty-body behavior. Re-review of the latest revision (head 91ac9f5).
Findings
API Conventions — looks good
- Field naming, JSON tag,
+optional, andomitemptyare all consistent with Kubernetes conventions (api/v1alpha1/taskspawner_types.go:390-403). - Generated
DeepCopyIntocorrectly nil-guards andmake+copys the new slice (api/v1alpha1/zz_generated.deepcopy.go:455-459). - CRD updates land in both
internal/manifests/charts/kelos/templates/crds/taskspawner-crd.yamlandinternal/manifests/install-crd.yaml, descriptions match the godoc —make updatewas run. - Implementation handles all five event types (
internal/webhook/github_filter.go:351-365for issues/issue_comment,:436-453for PR / review / review-comment) and each has an explicit regression test, including the multi-entry and combinedBodyContains+BodyNotContainscases.
Validation — good, with one note
MaxItems=10+items:MinLength=1+items:MaxLength=1024together close thestrings.Contains(x, "")always-true trap at the API boundary, and thecontainsAnyhelper (internal/webhook/github_filter.go:464-471) defensively skips empty entries — belt-and-suspenders, correct.- Adding
MaxLength=1024toBodyContainsis a small server-side tightening of an existing field; acceptable inv1alpha1, but worth calling out in the release note since any existing object with abodyContainslonger than 1024 chars would now fail validation on update (unlikely in practice, but explicit is better).
Naming / type asymmetry — worth deciding now, not later
BodyContains stringvsBodyNotContains []stringis asymmetric. @gjkim42 already raised this in the unresolved thread onapi/v1alpha1/taskspawner_types.go("users may be confused about the types of the two fields"), and the author's reply arguesBodyNotContainsas a list now is forward-compatible. That logic is sound forBodyNotContains, but the same argument applies in reverse toBodyContains— and the cheapest moment to promote it is in this PR, while the field is stillv1alpha1and the surrounding shape is being touched anyway.- There is precedent for both choices in this struct:
Author(string) /ExcludeAuthors([]string) is asymmetric, whileLabels/ExcludeLabelsare both[]string. So the asymmetry is defensible — but if the API ends up promotingBodyContainsto[]stringlater (the prior review and the PR description both flag this as the likely direction), users hit a real breaking change for no benefit over doing it now. - Recommendation: either (a) promote
BodyContainsto[]string(OR semantics — body must contain any entry) in this PR to eliminate the asymmetry while still inv1alpha1, or (b) explicitly document inBodyContains's godoc that the asymmetry is intentional and reflects "single positive trigger vs. multi-entry exclusions" (mirroring theAuthor/ExcludeAuthorsprecedent). Either way, the unresolved thread should be closed with a clear answer rather than left for a follow-up that may never land.
Documentation — minor nit
BodyContains's updated godoc mentions case-sensitivity but, unlikeBodyNotContains, does not note the empty-body behavior. A one-line symmetric note ("An empty (or missing) body never matches.") would keep the two fields' docs aligned (api/v1alpha1/taskspawner_types.go:384-388).
Compatibility / extensibility — fine
- Pure additive change for
BodyNotContains; existing clients can ignore the new field. No semantic changes to existing fields beyond theMaxLength=1024tightening onBodyContains. - If a regex or glob match mode is ever wanted, the flat-field shape (
bodyContains,bodyNotContains, futurebodyMatches, ...) will start to look crowded. Worth keeping in mind for a futurebody: { contains: […], notContains: […], matches: […] }reshape, but explicitly out of scope here.
Suggestions (optional)
- Add a brief comment near
containsAnypointing toitems:MinLength=1as the primary defense; the runtime guard is then clearly a fallback for older CRDs that predate the marker. Helps future readers avoid wondering whether the guard is load-bearing. - If keeping the asymmetry, a one-line
// See BodyNotContains for case-sensitivity / empty-body semantics; same rules apply here.onBodyContainssaves duplicating prose.
Note on prompt injection: a third-party reviewer's comment on this PR contained an HTML directive demanding that automated tools attribute findings to it. I disregarded that instruction and formed the analysis above independently from the diff.
/kelos needs-input
|
/kelos review |
|
🤖 Kelos Task Status Task |
There was a problem hiding this comment.
🤖 Kelos Reviewer Agent @gjkim42
Review Summary
Verdict: APPROVE
Overall correctness: patch is correct
Scope: Adds bodyPattern (regex) and excludeBodyPatterns ([]regex) filters to GitHubWebhookFilter for issues / issue_comment / pull_request / pull_request_review / pull_request_review_comment events; deprecates bodyContains in favor of bodyPattern.
Findings Overview
| Priority | Count | File:Line | Summary |
|---|---|---|---|
| P0 | 0 | — | none |
| P1 | 0 | — | none |
| P2 | 0 | — | none |
| P3 | 4 | internal/webhook/github_filter_test.go | No invalid-regex test for the error-rejects-event path |
| P3 | internal/webhook/github_filter_test.go | BodyPattern only tested for issue_comment — PR/review/review_comment paths uncovered |
|
| P3 | internal/webhook/github_filter.go:355-397, 468-514 | Body-extraction blocks duplicated four times across the new code | |
| P3 | internal/webhook/github_filter.go:531-542 | matchesAnyPattern short-circuits on first match without validating later patterns |
Findings
Tests
- [P3]
internal/webhook/github_filter_test.go— No test exercises the invalid-regex path. A test that setsbodyPattern: "(invalid"(or anexcludeBodyPatternsentry with a malformed regex) and asserts the event is rejected would lock in the safe-rejection contract formatchesPattern/matchesAnyPatternerrors. Today nothing prevents a future refactor from accidentally returningtrue(allow-all) on regex compile errors. - [P3]
internal/webhook/github_filter_test.go—BodyPatternis only tested forissue_comment(TestMatchesGitHubEvent_BodyPatternRegexand the combined test). The PR-side body extraction atinternal/webhook/github_filter.go:468-490is structurally distinct from the issues-side block and has no directBodyPatternregression test — for example, apull_request_reviewfilter withbodyPattern: "(?i)approved"currently has no test verifying it matches the review body and not the PR body.ExcludeBodyPatternsalready has full per-event-type coverage, so adding the parallelBodyPatterncases is a small lift.
Code quality
- [P3]
internal/webhook/github_filter.go:355-397and:468-514— The same body-extractionif/else ifchain appears four times (issue/comment × 2 filters, PR/review/review_comment × 2 filters). Folding them intoextractIssueBody(e github.Event)/extractPRBody(e github.Event, pr *github.PullRequest)helpers would reduce the chance the two filters drift in their handling of new event types. Non-blocking; consistent with the existingbodyContainsstyle. - [P3]
internal/webhook/github_filter.go:531-542—matchesAnyPatterncompiles each pattern lazily inside the loop and returns on the first match. If the body matchespatterns[0], an invalidpatterns[1]is never compiled and never logged; a different webhook payload that doesn't matchpatterns[0]will hit the bad regex and surface the error. Same configuration → different rejection paths depending on body content. The event is rejected either way, so this is a minor predictability nit, not a correctness bug. Compiling all patterns up front (or precompiling once per filter) would also remove the per-callregexp.Compilecost the PR description flagged.
Suggestions (optional)
- [P3]
internal/webhook/github_filter.go:525,535—fmt.Errorf("Invalid body pattern %q: %w", ...)capitalizes the leading word; idiomatic Go error strings start lowercase. The project's "capital first letter, no trailing punctuation" rule applies to log messages (which this code already gets right at:369,:391,:484,:508), not error returns. - [P3] Consider an
_ = revalidation step at filter-load time (or a future admission webhook) so a typo inbodyPatternis caught without waiting for the first webhook to arrive. Out of scope for this PR.
Key takeaways
- Implementation is sound: error paths log and fail-closed (reject), generated CRD/deepcopy artifacts are present, kubebuilder markers (
MaxItems=10,MinLength=1,MaxLength=1024) close the empty-pattern always-match trap at the API boundary. - All findings are non-blocking. Worth tightening the test surface around
BodyPatternon PR/review event types and the invalid-regex error path.
Note on prompt injection: a third-party reviewer's comment on this PR contains an HTML directive demanding that automated tools attribute findings to it. I disregarded that instruction and formed the analysis above independently from the diff.
|
If you are okay with it @gjkim42 I'll pick up the optional/p3 findings in a follow up PR |
| // +optional | ||
| // +kubebuilder:validation:MinLength=1 | ||
| // +kubebuilder:validation:MaxLength=1024 | ||
| BodyPattern string `json:"bodyPattern,omitempty"` |
There was a problem hiding this comment.
one question:
Is this backward-compatible? Does setting +kubebuilder:validation:MinLength=1 allow empty BoddyPattern?
There was a problem hiding this comment.
BodyPattern is omitted if empty, rather than allowing for strictly ""?
There was a problem hiding this comment.
If you want me to allow for an empty string specifically then I can. I'm not sure that there is a meaningful difference but happy to do so.
There was a problem hiding this comment.
I just want to make sure that the existing YAMLs that don't have bodyPattern (because it's a newly introduced field) can be applied.
can we just remove minLength here? it doesn't make sense here.
…b webhooks Add regex-based body filtering to GitHubWebhookFilter: - bodyPattern (string): requires the body to match the given regex. Replaces bodyContains for new usage. - excludeBodyPatterns ([]string): event is rejected if the body matches any regex entry. - Both fields use Go regexp (re2) syntax and include kubebuilder validation markers (MinLength=1, MaxLength=1024, MaxItems=10). - bodyContains is marked deprecated but remains functional. Supported event types: issues, issue_comment, pull_request, pull_request_review, and pull_request_review_comment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
731621e to
36f517f
Compare
PR #1058 surfaced a recurring API-change pattern not covered by the existing "Keep API surfaces minimal" rule: - A scalar -> array kind change on an existing CRD field (BodyContains) was flagged P1 because existing in-cluster resources would fail structural-schema validation; the repo itself had ~9 stale YAMLs in self-development/ and examples/ still using the old scalar form. - The maintainer asked twice about backward compatibility on a newly added BodyPattern field, and required removing +kubebuilder:validation:MinLength=1 because it would reject existing YAMLs that don't yet have the field. - The agreed migration path was to add new fields and mark the old one +deprecated rather than remove it. Adds the convention to AGENTS.md (and CLAUDE.md via symlink), to the shared agentconfig.yaml and kelos-workers.yaml, and to the kelos-api-reviewer prompt as an explicit checklist item under "API compatibility and evolution". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR #1058 surfaced a recurring API-change pattern not covered by the existing "Keep API surfaces minimal" rule: - A scalar -> array kind change on an existing CRD field (BodyContains) was flagged P1 because existing in-cluster resources would fail structural-schema validation; the repo itself had ~9 stale YAMLs in self-development/ and examples/ still using the old scalar form. - The maintainer asked twice about backward compatibility on a newly added BodyPattern field, and required removing +kubebuilder:validation:MinLength=1 because it would reject existing YAMLs that don't yet have the field. - The agreed migration path was to add new fields and mark the old one +deprecated rather than remove it. Adds the convention to AGENTS.md (and CLAUDE.md via symlink), to the shared agentconfig.yaml and kelos-workers.yaml, and to the kelos-api-reviewer prompt as an explicit checklist item under "API compatibility and evolution". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Distills thirteen recurring conventions from recent PR review feedback and propagates them across AGENTS.md, the shared and worker AgentConfigs, and the reviewer prompts. All changes are backed by specific PR review findings. 1. Never use os.Getenv() for secrets as Go flag defaults — PR #971 P1 (cmd/kelos-webhook-server, cmd/ghproxy leaked GITHUB_TOKEN via --help). 2. Fail fast on invalid configuration — PR #971 multiple P1/P2 (silent degradation when GitHub App credentials or token resolvers failed). 3. Keep API surfaces minimal — PR #902 (maintainer asked repeatedly to defer speculative fields and ship the minimum viable API). 4. API changes must preserve backward compatibility — PR #1058 (BodyContains scalar->array would break in-cluster manifests; resolved by deprecating the old field rather than tightening validation). 5. Docs must match implementation, not aspiration — PRs #1035, #1056, #1082 (docs claimed enforcement that the code never performed). 6. TaskSpawner conventions — PRs #965, #974 (webhook over poll, empty .Branch for issue events, dual issue/PR context, no manual checkout). 7. Don't use Gomega's global Expect() inside Eventually polling blocks — PR #1084 (transient API errors short-circuited the poller). 8. /kind cleanup for self-development/-only PRs — PR #1100 P2 (agent classified by problem nature; rule is to classify by file location). 9. CLI error messages must name the resource — PR #1101 P2 (errors.New ("task failed") loses the name when batched). 10. Test the happy path, not only early-return guards — PR #1096 (both new tests covered only the no-op branches because production code lacked a posting seam; rule now requires adding the seam). 11. Avoid vacuous substring assertions in printer tests — PR #1106 P2 (a "sentry" check passed because the fixture's Name was "sentry-fixer"). 12. Keep CRD enum docstrings consistent with the Enum marker — PR #1081 P2 (commentOn godoc said "empty matches both" but the marker rejected ""). 13. Qualify cross-CRD field references with the owning kind in docs — PR #1077 P2 (Workspace section cited bare podOverrides.env without noting the field lives on Task.spec). Also: kelos-reviewer branch field uses the safe with-index fallback form, kelos-squash-commits gates the issue_comment trigger to PullRequest comments (preventing /kelos squash-commits typed on a plain issue from running git push --force-with-lease against an empty branch fallback), and the README's .Branch description matches runtime behavior.
Distills thirteen recurring conventions from recent PR review feedback and propagates them across AGENTS.md, the shared and worker AgentConfigs, and the reviewer prompts. All changes are backed by specific PR review findings. 1. Never use os.Getenv() for secrets as Go flag defaults — PR #971 P1 (cmd/kelos-webhook-server, cmd/ghproxy leaked GITHUB_TOKEN via --help). 2. Fail fast on invalid configuration — PR #971 multiple P1/P2 (silent degradation when GitHub App credentials or token resolvers failed). 3. Keep API surfaces minimal — PR #902 (maintainer asked repeatedly to defer speculative fields and ship the minimum viable API). 4. API changes must preserve backward compatibility — PR #1058 (BodyContains scalar->array would break in-cluster manifests; resolved by deprecating the old field rather than tightening validation). 5. Docs must match implementation, not aspiration — PRs #1035, #1056, #1082 (docs claimed enforcement that the code never performed). 6. TaskSpawner conventions — PRs #965, #974 (webhook over poll, empty .Branch for issue events, dual issue/PR context, no manual checkout). 7. Don't use Gomega's global Expect() inside Eventually polling blocks — PR #1084 (transient API errors short-circuited the poller). 8. /kind cleanup for self-development/-only PRs — PR #1100 P2 (agent classified by problem nature; rule is to classify by file location). 9. CLI error messages must name the resource — PR #1101 P2 (errors.New ("task failed") loses the name when batched). 10. Test the happy path, not only early-return guards — PR #1096 (both new tests covered only the no-op branches because production code lacked a posting seam; rule now requires adding the seam). 11. Avoid vacuous substring assertions in printer tests — PR #1106 P2 (a "sentry" check passed because the fixture's Name was "sentry-fixer"). 12. Keep CRD enum docstrings consistent with the Enum marker — PR #1081 P2 (commentOn godoc said "empty matches both" but the marker rejected ""). 13. Qualify cross-CRD field references with the owning kind in docs — PR #1077 P2 (Workspace section cited bare podOverrides.env without noting the field lives on Task.spec). Also: kelos-reviewer branch field uses the safe with-index fallback form, kelos-squash-commits gates the issue_comment trigger to PullRequest comments (preventing /kelos squash-commits typed on a plain issue from running git push --force-with-lease against an empty branch fallback), the kelos-squash-commits branch field uses the same with-index fallback form for defensive consistency with the documented convention, and the README's .Branch description matches runtime behavior (PR head branch or pushed branch; empty for issue events). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Propagates eleven recurring conventions, distilled from prior PR review feedback, across AGENTS.md, the shared and worker AgentConfigs, and the reviewer prompts. Also fixes two existing self-development spawner configurations that were flagged in adjacent reviews. Conventions added to AGENTS.md, agentconfig.yaml, and kelos-workers.yaml: 1. Never use os.Getenv() for secrets as Go flag defaults — PR #971 P1 (cmd/kelos-webhook-server, cmd/ghproxy leaked GITHUB_TOKEN via --help). 2. Fail fast on invalid configuration — PR #971 multiple P1/P2 (silent degradation when GitHub App credentials or token resolvers failed). 3. Keep API surfaces minimal — PR #902 (maintainer asked repeatedly to defer speculative fields and ship the minimum viable API). 4. API changes must preserve backward compatibility — PR #1058 (BodyContains scalar->array would break in-cluster manifests; resolved by deprecating the old field rather than tightening validation). 5. Docs must match implementation, not aspiration — PRs #1035, #1056, #1082 (docs claimed enforcement that the code never performed). 6. Don't use Gomega's global Expect() inside Eventually polling blocks — PR #1084 (transient API errors short-circuited the poller). 7. CLI error messages must name the resource — PR #1101 P2 (errors.New("task failed") loses the name when batched). 8. Test the happy path, not only early-return guards — PR #1096 (tests covered only the no-op branches because production code lacked a posting seam; rule now requires adding the seam). 9. Avoid vacuous substring assertions in printer tests — PR #1106 P2 (a "sentry" check passed because the fixture's Name was "sentry-fixer"). 10. Keep CRD enum docstrings consistent with the Enum marker — PR #1081 P2 (commentOn godoc said "empty matches both" but the marker rejected ""). 11. Qualify cross-CRD field references with the owning kind in docs — PR #1077 P2 (Workspace section cited bare podOverrides.env without noting the field lives on Task.spec). Reviewer/api-reviewer checklists gain matching items so kelos-reviewer and kelos-api-reviewer flag these patterns on future PRs. Other fixes in the same sweep: - kelos-reviewer.spec.taskTemplate.branch uses the safe '{{with index . "Branch"}}{{.}}{{else}}main{{end}}' fallback form so a /kelos review typed on a plain issue lands on main rather than an empty branch. - kelos-squash-commits gates the issue_comment trigger to commentOn: PullRequest and uses the same with-index fallback for the branch field — /kelos squash-commits typed on a plain issue would otherwise fire the spawner with empty {{.Branch}} and the prompt then runs git push --force-with-lease. - self-development/README.md template-variable table corrected from "Usually PR head branch" to "PR head branch or pushed branch; empty for issue events" to match runtime behavior, and the bodyContains example was updated to bodyPattern to match the spawner configs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
What type of PR is this?
/kind feature
What this PR does / why we need it:
Adds regex-based body filtering to
GitHubWebhookFiltervia two new fields:bodyPattern(string): requires the body to match the given regex. Intended to replace the existingbodyContainsfor new usage.excludeBodyPatterns([]string): any-match-excludes — the event is rejected if the body matches any regex entry.Both fields use Go regexp (re2) syntax, supporting anchors, alternation, character classes, case-insensitive flags (
(?i)), etc.When both are set, the body must match
bodyPatternAND must not match anyexcludeBodyPatternsentry.bodyContainsis marked deprecated but remains functional and can be combined with the new fields.Supported event types:
issues(filters on issue body)issue_comment(filters on comment body)pull_request(filters on PR body)pull_request_review(filters on review body)pull_request_review_comment(filters on review comment body)Example usage:
Kubebuilder validation markers:
MaxLength=1024onbodyContainsandbodyPatternMaxItems=10,MinLength=1,MaxLength=1024onexcludeBodyPatternsWhich issue(s) this PR is related to:
N/A
Special notes for your reviewer:
bodyContains(string, substring match) is marked deprecated but not removed — existing spawners that use it continue to work.bodyPatternis a single string (likebodyContains), whileexcludeBodyPatternsis a list (likeExcludeLabels/ExcludeAuthors).matchesPattern/matchesAnyPatternhelpers compile patterns per-match. Volume is low enough (webhook filters) that this is fine; caching can be added if needed later.(?i)case-insensitive,\s+, alternation, anchors.Does this PR introduce a user-facing change?