Repository navigation
feat(hooks): log per-handler dispatch timings - #998
Open
CarlosWonMore wants to merge 1 commit into
Open
CarlosWonMore wants to merge 1 commit into
CarlosWonMore wants to merge 1 commit into
Conversation
`dispatch()` runs its matched handlers through `Promise.allSettled`, so the dispatcher's wall-clock is the slowest handler rather than the sum of them — but nothing recorded which handler that was. A hook that takes seconds only says `exceeded timeout of 4500ms` when a handler overruns, and says nothing at all when several merely add up under the host's cap. On Windows a `UserPromptSubmit` was spending ~4.7s against the host's 10s limit with no way to attribute it. Log each handler's wall-clock, slowest first, at debug level. The timings are observation only: `withTimeout` still enforces each handler's own `timeoutMs` budget, and the caller's ordering is untouched. A handler that rejects is recorded and the error still propagates to the caller unchanged. On a Windows run this attributes the time to module loading rather than to any handler's own work — `pending-hint` and `dashboard-report` each spend ~2.0-2.6s and ~1.8-2.2s, while `local-agent-sync`, `package-pending-hint` and `track-slash` stay at 9-64ms. Both slow handlers `import()` the same `dashboard-collector.js` graph, and their cost disappears when a handler exits before reaching that import. The single-handler case is ~51ms, so the bulk of the dispatch is loading modules the handler then never uses. Naming the slowest handler separately makes that split legible without arithmetic: `total` is the floor any one handler imposes, and `slowest` is how much of it that handler actually spent on its own work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
dispatch()runs its matched handlers throughPromise.allSettled, so the dispatcher's wall-clock is the slowest handler, not the sum — but nothing recorded which handler that was. A slow hook only saysexceeded timeout of 4500mswhen a handler overruns, and says nothing at all when several handlers merely add up under the host's cap. That makes the interesting case invisible.This logs each handler's wall-clock, slowest first, at debug level.
Observation only —
withTimeoutstill enforces each handler's owntimeoutMsbudget, the caller's ordering is untouched, and a rejecting handler still propagates its error unchanged.Why it matters
A real Windows
UserPromptSubmitwas spending ~4.7s against the host's 10s limit with no way to attribute it. The timings show it is not handler work:pending-hintdashboard-reportlocal-agent-syncpackage-pending-hinttrack-slashBoth slow handlers
import()the samedashboard-collector.jsgraph. A control run with acwdthat does not exist dropspending-hintfrom 2061 ms to 32 ms — it returns before reaching that import — whiledashboard-report, which goes on to resolve the scope, is unchanged. A dispatch with a single handler costs ~51 ms. So the bulk of a dispatch is loading modules the handler then never uses, which no timeout message can show.Naming
slowestnext tototalmakes that split legible without arithmetic:totalis the floor any one handler imposes,slowestis what it spent on its own work.Note this is not a sum of handler times —
Promise.allSettledalready prevents that, so a single slow handler and several slow handlers look the same from the outside. The gap betweentotalandslowestis time spent before the handler's own work began.Type of Change
Test Plan
npx tsc --noEmit— passesnpm run lint— 0 warnings, 0 errors on 811 files. (--type-aware/tsgolintcould not run in my sandbox: its subprocess fails to spawn withos error 231, an environment limit rather than a code signal.)~/.teamai/debug.log, onprompt-submit,post-tool-use(foreground and background) andsession-start; hook output is byte-identical with and without the change.npx vitest run— not a usable gate in my environment.hook-dispatch-cli.test.tsandhook-dispatch-scope.test.tsfail identically with and without this change (spawnSync git EBUSYon Windows, plus 15s test timeouts). I verified the baseline by stashing the change and re-running: same failure counts (4 failed / 18 passed and 7 failed respectively).builtin-hooks.test.tspasses fully.Implementation notes
.then()wrappers around the existingwithTimeout(...)promise, so nothing about the timeout or the concurrency changes.settledsees exactly what it saw before.ok/failed/timeoutrather than the raw error message: the message already reaches the log viahook-dispatch-cli's existinghandler "X" failed:line, and keeping it out avoids duplicating content that can carry paths or URLs.timeoutis separated out because that is the case the existing line cannot explain.