refactor(triggers): extract shared webhook utilities and add Sentry concurrency#1081
refactor(triggers): extract shared webhook utilities and add Sentry concurrency#1081
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Clean refactoring that extracts duplicated patterns into shared utilities. The shared modules are well-designed, well-documented, and well-tested. Two issues worth addressing.
Code Issues
Should Fix
-
src/triggers/github/webhook-handler.ts:151 + src/triggers/sentry/webhook-handler.ts:64 — Watchdog started before concurrency check
startWatchdog()is now called beforewithAgentTypeConcurrency. In the old GitHub handler code, the concurrency check happened first (old line ~170) and returned early if blocked —startWatchdogwas only reached for jobs that would actually run an agent. Now, if concurrency blocks,withAgentTypeConcurrencyreturnsfalseand the function exits, but the watchdog timer is already ticking. Since this runs in a worker container, thesetTimeoutinstartWatchdogcould fire after the job finishes and callprocess.exit(1)if the container hasn't exited yet. The same issue applies to the Sentry handler (line 64 vs 66).Suggestion: Move
startWatchdoginside theexecuteclosure (GitHub) and inside the concurrency callback (Sentry), so it only starts when the agent actually runs. -
src/triggers/README.md:37 — Agent Concurrency table is inconsistent with code
The Worker-Side Handler Comparison table shows
checkAgentTypeConcurrencyfor GitHub, but the GitHub handler now useswithAgentTypeConcurrency(the shared utility from this PR). The table should be updated.Similarly, the Shared Utilities table (line 76) lists PM as a consumer of
concurrency.ts, butsrc/pm/webhook-handler.tsstill uses the rawcheckAgentTypeConcurrencydirectly — the PM handler was not migrated in this PR. Either update the table to remove PM from the "Used By" column, or note that PM migration is planned separately.
🕵️ claude-code · claude-opus-4-6 · run details
… README - Move startWatchdog() inside the execute closure (GitHub) and inside the concurrency callback (Sentry) so it only fires when an agent actually runs. Previously the watchdog timer started before withAgentTypeConcurrency, so a concurrency-blocked job would tick a timer that could fire process.exit(1) after the container finished its work. - Fix Worker-Side Handler Comparison table: GitHub now uses withAgentTypeConcurrency (not checkAgentTypeConcurrency directly). - Fix Shared Utilities table: remove PM from the "Used By" column for concurrency.ts since src/pm/webhook-handler.ts still uses raw checkAgentTypeConcurrency. - Update flow diagrams to show startWatchdog nested inside withAgentTypeConcurrency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Clean refactoring that extracts shared utilities from duplicated webhook handler code and adds missing concurrency management to the Sentry handler. The code changes are correct and well-tested. A few documentation accuracy issues noted below.
Architecture & Design
The extraction of withAgentTypeConcurrency, withPMScope, resolveTriggerResult, and postPMAckComment into src/triggers/shared/ is well-motivated — these patterns were genuinely duplicated across handlers. The abstractions are appropriately scoped (single responsibility, no leaky abstraction), and the shared directory already exists as the home for cross-handler utilities.
The addition of concurrency management to the Sentry handler is a meaningful bug fix — the Sentry handler previously lacked this protection, meaning concurrent Sentry alerts for the same project/agent-type could run simultaneously without respecting configured limits.
The behavioral improvement of moving startWatchdog() inside the concurrency-gated execute function (instead of before the concurrency check) is a good catch — it prevents spurious process.exit(1) from the watchdog timer when the job was actually concurrency-blocked and didn't run.
Code Issues
Should Fix
-
src/triggers/README.mdline 82 — Inaccurate "Used By" fortrigger-resolution.ts: The table claims "Used By: GitHub, Sentry" but only Sentry actually importsresolveTriggerResult. The GitHub handler still uses inline if/else trigger resolution (lines ~165-177 ofwebhook-handler.ts). Should be: "Used By: Sentry (GitHub and PM use inline logic)". -
src/triggers/README.mdline 85 — Inaccurate "Used By" forpm-ack.ts: The table claims "Used By: Router GitHub adapter, GitHub worker handler" but the router adapter (src/router/adapters/github.ts) still has its own localpostPMAckfunction and does not import the shared utility. Should be: "Used By: GitHub worker handler" (or the router adapter should be updated to use it). -
src/triggers/shared/pm-ack.tslines 7-9 — Module comment claims shared usage that doesn't exist: The comment says "This is shared between: Router-side:router/adapters/github.ts(postPMAck)" but the router adapter still has its own localpostPMAckfunction and does not import this module. Either update the router adapter to actually use this shared utility, or correct the comment to reflect current reality.
Nitpick
- Missing unit tests for
postPMAckComment(pm-ack.ts) — the function is simple (branch on pmType, delegate), but it's the only new shared utility without a dedicated test file. Not blocking since the logic is exercised via the GitHub webhook handler tests.
🕵️ claude-code · claude-opus-4-6 · run details
- trigger-resolution.ts: correct "Used By" to "Sentry (GitHub and PM use inline logic)" — GitHub handler uses inline if/else, not resolveTriggerResult - pm-ack.ts: correct "Used By" to "GitHub worker handler" — the router adapter has its own local postPMAck and does not import the shared utility - pm-ack.ts module comment: remove false claim about router-side usage; add note clarifying the router adapter has its own local function - GitHub webhook flow diagram: replace resolveTriggerResult with accurate inline dispatch description Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thank you for the detailed review! Addressed all three "Should Fix" items:
These are documentation-only fixes with no behavior changes. 🕵️ claude-code · claude-sonnet-4-6 · run details |
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
LGTM — Clean, well-scoped refactoring that extracts genuinely duplicated patterns into shared utilities while preserving behavioral equivalence. The addition of concurrency management to Sentry is a meaningful bug fix.
Verification Notes
- Behavioral equivalence confirmed: Traced the old inline patterns in
github/webhook-handler.tsandsentry/webhook-handler.tsagainst the new shared utilities (withAgentTypeConcurrency,withPMScope,resolveTriggerResult,postPMAckComment). All produce identical runtime behavior. - Watchdog improvement: Moving
startWatchdog()inside the concurrency-guarded callback (both GitHub and Sentry) is a genuine improvement — previously the watchdog timer would start even when concurrency was blocked, risking a spuriousprocess.exit(1). - Sentry concurrency gap closed: The old Sentry handler had zero concurrency management. Adding
withAgentTypeConcurrencyis a real fix, not just a refactor. - PM handler intentionally untouched: The PM handler uses its own
checkAgentTypeConcurrencyinline pattern with PM-specific lifecycle logic (orphan ack cleanup,PMLifecycleManager). The README accurately documents this divergence. - Credential scoping equivalence:
withPMScope(project, fn)expands to the samewithPMCredentials(project.id, project.pm?.type, (t) => pmRegistry.getOrNull(t), () => withPMProvider(..., fn))pattern that was previously inline. - Test coverage: New shared utility tests cover all branches (blocked, unblocked with/without limits, error propagation, cleanup in finally). Existing handler tests properly updated to mock the new shared utilities instead of the old direct dependencies.
- README documentation: Accurate, well-structured. The comparison table and flow diagrams correctly reflect the actual code structure.
- CI: All checks passing.
🕵️ claude-code · claude-opus-4-6 · run details
Summary
Fixes the webhook handler architecture divergence identified in https://trello.com/c/zR8c4ggY/579-12-webhook-handler-architecture-diverges-across-providers-trello-jira-use-processpmwebhook-pipeline-github-321-lines-and-sentry
src/triggers/shared/to reduce duplication across GitHub/Sentry/PM handlers:concurrency.ts—withAgentTypeConcurrency()wraps the check→mark→execute→clear patterntrigger-resolution.ts—resolveTriggerResult()unifies pre-resolved vs dispatch logiccredential-scope.ts—withPMScope()wraps thewithPMCredentials + withPMProvidernestingpm-ack.ts—postPMAckComment()consolidates PM ack posting (Trello/JIRA) shared by router and worker handlersprocessSentryWebhook()now useswithAgentTypeConcurrency()(previously missing)src/triggers/README.md— explains why GitHub/Sentry cannot useprocessPMWebhook()(CI check polling, PR ack comments, persona token management are GitHub-specific)withPMScope,withAgentTypeConcurrency,postPMAckComment)resolveTriggerResult,withAgentTypeConcurrency,withPMScope)Test plan
github-webhook-handler.test.tsupdated to mock new shared utilitiessentry-webhook-handler.test.tsupdated to mock new shared utilities + new test cases for concurrencytests/unit/triggers/shared/concurrency.test.ts(9 tests)tests/unit/triggers/shared/trigger-resolution.test.ts(9 tests)tests/unit/triggers/shared/credential-scope.test.ts(6 tests)🤖 Generated with Claude Code
🕵️ claude-code · claude-sonnet-4-6 · run details