Skip to content

fix(linear-bot): dedupe webhooks by Linear-Delivery header, not webhook configuration ID#627

Merged
ColeMurray merged 2 commits into
ColeMurray:mainfrom
watchdog-no:fix/linear-bot-dedup-key-upstream
May 17, 2026
Merged

fix(linear-bot): dedupe webhooks by Linear-Delivery header, not webhook configuration ID#627
ColeMurray merged 2 commits into
ColeMurray:mainfrom
watchdog-no:fix/linear-bot-dedup-key-upstream

Conversation

@hreiten
Copy link
Copy Markdown
Contributor

@hreiten hreiten commented May 12, 2026

Summary

The Linear bot dedupes incoming webhooks using the body's webhookId field. Per Linear's webhook docs, that field is the registered-webhook configuration ID — a constant for every delivery to a given endpoint, not a per-delivery identifier. So once the first webhook of each hour writes its slot to KV, every subsequent delivery within the expirationTtl (3600s) hits the duplicate branch in isDuplicateEvent and is silently dropped with HTTP 200 { skipped: true, reason: "duplicate" }. From a user's perspective in Linear, the agent shows up as "Did not respond" on most issue assignments.

This switches the dedup key to the Linear-Delivery HTTP header, which Linear documents as a UUID v4 that "uniquely identifies the payload delivery." The route also fails closed with HTTP 400 when the header is absent, so a future Linear change surfaces in the webhook delivery log rather than silently degrading.

Reproduction

Live worker tail while reproducing — three unrelated AgentSessionEvent deliveries (different issues, minutes apart, including one freshly created) all carried the same body webhookId. The KV slot keyed on that ID was set on the first delivery and shadowed the next two until TTL expiry:

trace_id ...3794848a...  event_key <constant-uuid>
trace_id ...58d2a73a...  event_key <constant-uuid>
trace_id ...aa3ec06c...  event_key <constant-uuid>   ← brand-new issue

Changes

packages/linear-bot/src/index.ts+16/-13. The /webhook route now:

  1. Validates the AgentSessionEvent payload shape before reading any headers.
  2. Reads Linear-Delivery from request headers.
  3. Returns HTTP 400 with reason: "missing_linear_delivery_header" if the header is missing.
  4. Uses the header value as the KV dedup key (TTL unchanged).

Test plan

  • npm run typecheck -w @open-inspect/linear-bot
  • npm run lint -w @open-inspect/linear-bot
  • npm test -w @open-inspect/linear-bot — all 103 tests pass
  • Validated end-to-end on a production deployment: two fresh issues assigned within the same minute, both proceed through agent_session.receivedagent_session.session_created with distinct trace IDs and no spurious dedup.

Notes for reviewers

No new tests. src/index.ts is explicitly excluded from coverage in vitest.config.ts and the repo convention doesn't add route-level tests. The change is a single route edit with no extractable pure helper whose test would do more than restate the implementation.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced webhook delivery validation and duplicate detection for more reliable event processing.

Review Change Stack

The bot dedup'd webhooks on the body's `webhookId` field, which Linear
documents as the registered-webhook configuration ID — a constant for
every delivery to a given endpoint. Empirically observed: three
unrelated AgentSessionEvent deliveries across distinct issues all
carried `webhookId: a5d92758-d29b-4214-adef-f1e452fee526`. So the
first webhook of each hour poisoned the KV slot (TTL 3600s) and every
subsequent delivery within the TTL was silently dropped — surfacing in
Linear as "Anton did not respond."

Switch the dedup key to the `Linear-Delivery` HTTP header, which Linear
documents as a UUID v4 that "uniquely identifies the payload delivery."
Fail closed with 400 if the header is absent so a future Linear change
surfaces in the webhook delivery log rather than silently degrading.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ecd7fdc4-5519-4c23-8d24-281edc96f4f8

📥 Commits

Reviewing files that changed from the base of the PR and between b52ab9a and 54ae00a.

📒 Files selected for processing (1)
  • packages/linear-bot/src/index.ts

📝 Walkthrough

Walkthrough

The webhook handler for AgentSessionEvent now validates the request payload shape before deduplication, and switches dedupe tracking from the body webhookId field to the linear-delivery HTTP header. Missing headers are rejected with a 400 response.

Changes

AgentSessionEvent Webhook Handler Refactoring

Layer / File(s) Summary
Webhook shape validation and header-based deduplication
packages/linear-bot/src/index.ts
Request body is validated against AgentSessionWebhook shape up front, rejecting invalid payloads with 400. Deduplication now uses the linear-delivery header as the key; missing headers are rejected immediately, and duplicate deliveries return ok: true with skipped: true and the delivery UUID as the event key.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • ColeMurray/background-agents#460: Both PRs modify AgentSessionEvent webhook handling—adjusting payload validation timing and deduplication strategy (this PR switches to linear-delivery header versus the prior approach).

Suggested reviewers

  • ColeMurray
  • open-inspect

Poem

🐰 A header now guides our dedup way,
Linear-Delivery leads us astray from body fields,
Validation comes first—no more delays,
Webhooks now speak with headers that shield,
Cleaner, safer, the bot now yields! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main change: deduplication logic now uses the Linear-Delivery HTTP header instead of the webhook configuration ID (webhookId field).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@hreiten hreiten closed this May 13, 2026
@hreiten hreiten deleted the fix/linear-bot-dedup-key-upstream branch May 13, 2026 09:13
@hreiten hreiten restored the fix/linear-bot-dedup-key-upstream branch May 13, 2026 09:37
@hreiten hreiten reopened this May 13, 2026
@ColeMurray ColeMurray merged commit a8a14c4 into ColeMurray:main May 17, 2026
18 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.

2 participants