Skip to content

feat(webhooks): registry + delivery (slice 09)#128

Merged
sebyx07 merged 4 commits into
mainfrom
webhooks/registry-fanout
Jul 19, 2026
Merged

feat(webhooks): registry + delivery (slice 09)#128
sebyx07 merged 4 commits into
mainfrom
webhooks/registry-fanout

Conversation

@sebyx07

@sebyx07 sebyx07 commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • One WebhookRegistry consumed by CRUD routes + orchestrator fan-out (registered webhooks now actually receive events)
  • Fixed delivery semantics: emit() is non-blocking via background worker
  • api/routes_webhooks.py: call has_updates() on PUT empty body (400); wired/deleted unused WebhooksConfig
  • Tests: emitter fan-out (run.started delivery), max_retries=0→1 attempt (already in test_client.py), concurrent creates survive flock serialisation, kill-mid-save atomic write guarantee, emit non-blocking timing assertion

Test plan

  • tests/webhooks/test_emitter.py — 5 new tests covering fan-out and non-blocking emit
  • tests/webhooks/test_registry.pytest_kill_mid_save_leaves_original_intact added to TestTransaction
  • tests/api/test_routes_webhooks.pytest_concurrent_creates_both_survive + pre-existing mypy fix
  • Full suite: 5770 passed, 0 failures

🤖 Generated with Claude Code

sebyx07 and others added 4 commits July 19, 2026 07:30
One WebhookRegistry is now the single source of truth consumed by BOTH
the REST CRUD routes and the orchestrator's fan-out emitter, closing the
gap where registered webhooks never received any events.

- New webhooks/registry.py: flock-serialised read-modify-write
  transactions + shared atomic_write_json (durable, crash-safe), built on
  WebhookConfig; corruption-tolerant lock-free reads
- routes_webhooks.py: list/create/get/update/delete/test persist through
  the registry; mutations are atomic (concurrent POST no longer loses one,
  crash mid-save no longer truncates the file), typed conflict/not-found
- WebhookEmitter: fans each event out to every registered webhook filtered
  by should_send_event, alongside the CLI --webhook-url client
- Tests: registry unit suite + emitter fan-out routing

Delivery semantics (retry, non-blocking) remain for the next task.

Co-Authored-By: Claude <noreply@anthropic.com>
client.py:
- Retry loop bound attempt <= max_retries (1 initial try + N retries,
  matching DEFAULT_MAX_RETRIES intent); back off only when another
  attempt actually remains.
- Unify send/send_sync onto shared _success/_http_error/_exhausted
  result + _retryable_status_error/_log_retry/_backoff_delay helpers.

events.py:
- Base WebhookEvent.event_type is now required (field init=False, no
  default); __post_init__ raises TypeError if unset instead of silently
  mislabeling the abstract base as task.started.

orchestrator.py:
- Emit via a background single-worker queue so a slow/dead endpoint can
  never block the loop; add _drain_webhooks() (flush + stop worker),
  called in run()'s finally and the max-sessions early return so the
  terminal run.completed delivery isn't dropped under the daemon worker.

tests:
- Opt into synchronous delivery where delivery is asserted immediately;
  add TestWebhookEmitterAsyncDelivery covering off-thread dispatch,
  flush/close/drain, inline-after-close fallback, and run() drain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…bhooksConfig

- Add has_updates() validation to PUT /webhooks/{webhook_id}
  - Returns 400 if empty body (no fields provided)
  - Prevents silent no-op updates
- Delete WebhooksConfig class (unused, WebhookRegistry is the canonical implementation)
  - Remove from webhooks/config.py, webhooks/__init__.py
  - Remove from test_config.py (TestWebhooksConfig class)
  - Remove unused Iterator import
- Add test_update_webhook_no_updates to verify 400 response

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…n-blocking

- tests/webhooks/test_emitter.py (NEW): registered webhook receives
  run.started via synchronous fan-out; emit() returns < 0.5 s with a
  blocked delivery endpoint (background-worker timing assertion)
- tests/webhooks/test_registry.py: TestTransaction.test_kill_mid_save_*
  patches atomic_write_json to raise OSError and verifies the original
  webhooks.json is intact after the failed rename
- tests/api/test_routes_webhooks.py: TestCreateWebhook.test_concurrent_*
  sends two POST /webhooks requests from a ThreadPoolExecutor and asserts
  both 201 and both URLs in the registry; also fixes pre-existing mypy
  var-annotated error on update_data in test_update_webhook_no_updates

Co-Authored-By: Claude <noreply@anthropic.com>
@sebyx07 sebyx07 added the claudetm PRs created by Claude Task Master label Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 56 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: d89f1d45-3565-4d46-b032-62916a7aa4af

📥 Commits

Reviewing files that changed from the base of the PR and between 560a526 and 1b849d7.

📒 Files selected for processing (14)
  • src/claude_task_master/api/routes_webhooks.py
  • src/claude_task_master/core/orchestrator.py
  • src/claude_task_master/webhooks/__init__.py
  • src/claude_task_master/webhooks/client.py
  • src/claude_task_master/webhooks/config.py
  • src/claude_task_master/webhooks/events.py
  • src/claude_task_master/webhooks/registry.py
  • tests/api/test_routes_webhooks.py
  • tests/core/test_orchestrator_webhooks.py
  • tests/webhooks/test_client.py
  • tests/webhooks/test_config.py
  • tests/webhooks/test_emitter.py
  • tests/webhooks/test_events.py
  • tests/webhooks/test_registry.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch webhooks/registry-fanout

Comment @coderabbitai help to get the list of available commands.

@sebyx07
sebyx07 merged commit f671779 into main Jul 19, 2026
11 checks passed
@sebyx07
sebyx07 deleted the webhooks/registry-fanout branch July 19, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claudetm PRs created by Claude Task Master

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant