Skip to content

feat(webhook): forward CP API key, add label trigger, optional review caps - #63

Merged
AbirAbbas merged 7 commits into
Agent-Field:mainfrom
jainakshay93:feat/webhook-auth-label-and-limits
Aug 8, 2026
Merged

feat(webhook): forward CP API key, add label trigger, optional review caps#63
AbirAbbas merged 7 commits into
Agent-Field:mainfrom
jainakshay93:feat/webhook-auth-label-and-limits

Conversation

@jainakshay93

Copy link
Copy Markdown
Contributor

What & why

Three improvements to the GitHub webhook handler (app.py), found running pr-af self-hosted with control-plane auth on a small shared host. All are opt-in / non-breaking.

1. Forward the control-plane API key (bug fix)

_fire_review POSTs to /api/v1/execute/async/pr-af.review with no auth header. When the control plane runs with auth enabled (AGENTFIELD_API_KEY set), that call 401s — so the webhook silently never dispatches a review. Now it forwards AGENTFIELD_API_KEY as X-API-Key when set.

2. Label trigger (feature)

Adds a pull_request labeled handler: applying a configurable label (PR_AF_LABEL, default pr-af) to a PR fires a review — an alternative to the @pr-af comment mention. Handy where teams prefer a label, or where GitHub Actions can't run.

3. Optional review caps for webhook runs (feature)

Webhook-triggered reviews can now read optional per-deployment limits, applied only when set (default behaviour unchanged):

  • PR_AF_MAX_CONCURRENT_REVIEWERS
  • PR_AF_MAX_REVIEW_DEPTH
  • PR_AF_MAX_COVERAGE_ITERATIONS
  • PR_AF_IGNORE_PATHS (comma-separated)

This lets a small/shared host bound resource use (e.g. max_concurrent_reviewers=1, max_review_depth=0) without patching code — the default fan-out (concurrency 8, deep sub-reviews) can exhaust memory on a modest box and deadlock on very large PRs.

Notes

🤖 Generated with Claude Code

… caps

Three improvements to the GitHub webhook handler, found running pr-af
self-hosted with control-plane auth on a small shared host:

1. Forward AGENTFIELD_API_KEY as X-API-Key on the review-dispatch call.
   Without it, the webhook silently 401s whenever the control plane has
   auth enabled, so no webhook-triggered review ever runs.

2. Trigger a review when a configurable label (PR_AF_LABEL, default
   'pr-af') is added to a PR — an alternative to the @mention comment.

3. Optional per-deployment review caps for webhook runs, applied only
   when set (PR_AF_MAX_CONCURRENT_REVIEWERS / PR_AF_MAX_REVIEW_DEPTH /
   PR_AF_MAX_COVERAGE_ITERATIONS / PR_AF_IGNORE_PATHS). Lets a small or
   shared host bound resource use without a code change; default
   behaviour is unchanged when unset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Aug 3, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

AbirAbbas and others added 6 commits August 8, 2026 11:58
…d caps

The Go node is the default install since Agent-Field#64, but its webhook rejected
every pull_request event and fireReview sent no auth header, so the 401
fix and the new label/caps features only existed on the Python node.
Mirror all three: conditional X-API-Key from AGENTFIELD_API_KEY, a
configurable PR_AF_LABEL labeled-event trigger with delivery-ID LRU and
per-PR TTL dedupe, and request-time cap parsing with the same input keys
and validation as the Python side. Tests use an injected transport so no
listening socket is needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The node registers under NODE_ID but _fire_review always posted to
pr-af.review, so any deployment overriding NODE_ID dispatched to a
reasoner that does not exist. The Go port already uses n.NodeID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… PR_AF_IGNORE_PATHS

Malformed cap values escaped _fire_review's exception handler as webhook
500s, and concurrency 0 / negative values produced hangs or crashes:
non-integer or below-minimum values are now logged and ignored
(concurrency >= 1, depth >= 0, iterations >= 1). Label-triggered
dispatches now dedupe GitHub delivery IDs (bounded LRU) and rate-limit
per PR URL (10 min TTL, in-memory by design — single-process webhook).
PR_AF_IGNORE_PATHS is removed instead of shipped inert: nothing in
either pipeline reads ignore_paths yet; real diff filtering is
follow-up work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With max_concurrent_reviewers=1 — the headline use case for the new
webhook caps — a dimension that spawned a sub-review deadlocked: the
parent held the sole semaphore permit while awaiting a child that could
never acquire one. Await spawned children after the parent's permit is
released. The Go orchestrator is structurally immune (parents return
before children acquire).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR_AF_LABEL and the three cap variables were configurable but invisible
to af config / Desktop because neither agentfield-package.yaml declared
them; the Go README env table now lists them too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AbirAbbas

Copy link
Copy Markdown
Contributor

Pushed a takeover pass to get this merge-ready — the base feature is unchanged, thanks @jainakshay93. What's new on top of the original commit (plus a merge of current main):

  • Go node parity (the default install since Installing this repo gives you the Go node, as pr-af #64): the Go webhook previously rejected every pull_request event and sent no auth header, so none of this PR's fixes reached default installs. X-API-Key forwarding, the PR_AF_LABEL labeled-trigger, and the cap payload now also live in go/internal/node/webhook.go, with tests using an injected transport.
  • NODE_ID dispatch fix: _fire_review posted to hard-coded pr-af.review; it now uses the configured NODE_ID (the Go side already did).
  • Cap validation on both sides: malformed or below-minimum values are logged and ignored instead of 500ing the webhook (concurrency ≥ 1, depth ≥ 0, iterations ≥ 1 — concurrency 0/negative previously hung or crashed).
  • Concurrency-1 deadlock fix: with max_concurrent_reviewers=1 (this PR's headline use case) a spawned sub-review deadlocked behind its parent's semaphore permit; parents now release their permit before awaiting children, with a regression test. The Go orchestrator is structurally immune.
  • Label-trigger dedupe: bounded delivery-ID LRU + 10-minute per-PR TTL on both nodes, so redeliveries and bot label loops can't fire duplicate paid reviews (in-memory by design — single-process webhook; noted in code).
  • Dropped PR_AF_IGNORE_PATHS: it was parsed and forwarded but nothing in either pipeline reads ignore_paths, so it was an inert knob. Real diff filtering is follow-up work.
  • Manifests/docs: PR_AF_LABEL and the three cap vars are now declared in both agentfield-package.yamls and the Go README env table.

Gates: ruff clean, pytest 79 passed, go build/vet/test/gofmt clean, CI green.

@AbirAbbas
AbirAbbas merged commit 95db33a into Agent-Field:main Aug 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants