feat(logs): let the sink serve on_output hooks - #668
Conversation
|
Warning Review limit reached
Next review available in: 17 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
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. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 75233b0. Configure here.
Greptile SummaryThe PR moves non-PTY
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; readiness-only reports no longer fire differently filtered output hooks, and the retained supervisor debounce prevents replacement sinks from firing twice within one semantic debounce window. Important Files Changed
Reviews (4): Last reviewed commit: "chore: re-render the usage file for clap..." | Re-trigger Greptile |
|
Both findings were real; fixed in 17589b6. Readiness reports triggering output hooks (@greptile-apps P1, @cursor). Straightforwardly my bug. A daemon can have The report now says why it was sent. Sink replacement resetting the debounce (@greptile-apps P1). Also right. A replacement sink starts with The debounce is now applied on both sides instead of bypassed supervisor-side. My original reasoning for the bypass — that the supervisor's clock starts later and would suppress a firing the sink allowed — turns out to be worth very little: its New unit test, 541 unit tests; 52 bats across hooks and logs. On the CI check: the only non-passing entry is This comment was generated by Claude Code. |
|
Both fixes look correct. For the readiness/hook conflation: For the double-fire after sink replacement: the reasoning holds. The Both P1s are resolved cleanly. No further concerns from me on this PR. |
17589b6 to
96e25ef
Compare
An `on_output` hook was the last thing forcing a daemon onto in-process capture, which put the supervisor back in its data path: killing the supervisor left the pipe readerless, the daemon took SIGPIPE, and both its logging and its hook stopped. The sink already reads every line, so it applies the hook's filter and its debounce and reports the lines that qualify. Debouncing there rather than in the supervisor means one message per window instead of one per line, which matters for a hook with no filter, where every line matches. The supervisor fires the hook on what it is told without re-deciding either question — its own clock started later and would suppress firings the sink correctly allowed. `OutputLine` now carries where it came from rather than a bare persist flag, because the answer drives two decisions, not one: whether to store the line, and whether it still needs filtering. Only PTY daemons remain on the in-process path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A daemon can have both `ready_output` and an `on_output` hook filtering for something else. Treating every sink-relayed line as hook-worthy ran the hook's command for the line that merely announced readiness. The sink now says why it reported a line, and the supervisor fires the hook only when the sink says the line passed its filter. The debounce is applied on both sides rather than bypassed: a replacement sink starts with a fresh clock, which would otherwise let a hook fire twice inside one configured window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
96e25ef to
af2056f
Compare
|
Rebased onto The The three inline comments still showing on this PR are from 75233b0 and were fixed in the commit after it; GitHub re-anchors them onto the current diff. 551 unit tests, lint clean. This comment was generated by Claude Code. |

Stacked on #667 — review that one first; this PR's diff is against it.
The last exclusion
After #667, an
on_outputhook was the only non-PTY reason a daemon still used in-process capture, and so the only remaining reason for the supervisor to sit in a daemon's data path. Killing the supervisor left the pipe readerless, the daemon took SIGPIPE, and both its logging and its hook stopped — with the hook's owner, who is usually watching for something going wrong, none the wiser.The change
The sink already reads every line, so it applies the hook's
filter/regexand its debounce, and reports the lines that qualify. The supervisor fires the hook on what it is told.Why the debounce moves to the sink. A hook with neither
filternorregexfires on every line, so leaving the debounce supervisor-side would mean one IPC message per line of output. Applying it in the process that sees every line makes it one message per window.Why the supervisor does not re-apply it. Its
on_output_last_firedclock starts when the monitoring task does, which is later than the sink's, so re-deciding would suppress firings the sink correctly allowed. Relayed lines are fired directly; locally-read lines take the old path unchanged.OutputLinenow carries anOutputSourcerather than thepersistbool #667 introduced, because the answer drives two decisions rather than one: whether the line still needs storing, and whether it still needs filtering.LocalandSinksay that plainly where a bool named for one of them would not.The hook is validated before its config is handed to a sink, so an invalid one is dropped rather than passed along — the same thing in-process capture does with it, instead of falling through to the "no filter, no regex" arm and firing on every line.
Coverage after this
ready_outputon_outputpty = truePTY is genuinely different — output arrives on a terminal master, not a pipe — so it needs the sink to be handed the master fd rather than a pipe read end. Left alone, and the module docs say so.
Tests
on_output fires from a sink rather than in-process capture— hook fires, a sink owns the stream, and the line is stored exactly oncean on_output daemon keeps logging through a supervisor crash— SIGKILL the supervisor, daemon lives, output keeps accruingBoth bats tests were mutation-tested — restoring
on_output_hook.is_none()tois_supportedfails both. 539 unit tests; 56 bats across hooks, logs and pty.This PR was generated by Claude Code.
Note
Medium Risk
Changes daemon lifecycle, IPC, and when user-configured hook commands run; behavior is well-tested but mistakes could miss hooks or fire them incorrectly.
Overview
on_outputhooks now run with out-of-process log capture, so daemons with hooks no longer force the supervisor to read stdout/stderr in-process (PTY daemons still do).The
log-sinksidecar gains--report-outputplus filter/regex/debounce flags. It applies the hook’s matching and rate limit while draining the pipe, then reports qualifying lines over IPC asSinkOutputLine(replacing readiness-onlySinkReadyMatch) with afires_hookbit so readiness-only lines do not trigger the hook.Supervisor wiring uses
WatchForto passready_outputand validated hook config into the sink, registers an output relay when either is present, and replacesOutputLine::persistwithOutputSource(LocalvsSink { fires_hook }) for persistence and hook firing. The monitoring loop trustsfires_hookfor sink-relayed lines and still debounces locally on fallback paths.Docs/CLI specs are regenerated; bats and unit tests cover sink-driven hooks, crash-survivable logging, debounce, and readiness vs hook separation.
Reviewed by Cursor Bugbot for commit af2056f. Bugbot is set up for automated code reviews on this repo. Configure here.