Strip tools from webhook reply-gen + fair preempt yield + more color#517
Merged
Conversation
Three fixes for the webhook responsiveness regression observed on PR #515: 1. Reply-gen system prompt now forbids tool use. Opus was treating review comments as directives and firing Bash/Read/Edit calls to actually make the change — a 5s reply turned into a 4m 44s session turn that held the lock and starved the worker. `reply_system_prompt` in prompts.py now spells out 'no Bash, no Read, no Edit, no Write, no Grep, no Glob, no Task sub-agents, no WebFetch, no plan mode, no file modifications of any kind' and explains that the task queue will do the actual work later. 2. Fair preempt yield via `_preempt_pending` event. Python's RLock isn't FIFO-fair under contention, so a worker thread that released the lock on cancellation could race back in and re-acquire before the waiting webhook thread got scheduled — at which point iter_events' `_cancel.clear()` at turn start wiped the webhook's signal and the webhook waited for a full worker turn. prompt() now sets `_preempt_pending` right after _cancel.set(), __enter__ clears it on acquire, and the worker's `_run_session_turn` retry calls `session.wait_for_pending_preempt()` between release and re-acquire so the preempter actually gets the lock first. 3. More ip-color style coverage in kennel/status.py — repo name BOLD + state word GREEN/DIM separately, Issue/PR/Worker labels BOLD, tree glyphs DIM, claude pid suffix DIM, kennel: UP in GREEN and DOWN in RED_BOLD. Logging added on the preempt path: `session.prompt` logs preempt request, `wait_for_pending_preempt` logs cede + acquire-latency or timeout-warning.
Adds `ClaudeSession._log_event` that renders each stream-json event as a human-readable INFO line, invoked both inside iter_events and inside _drain_to_boundary (so a cancelled turn's tail is visible rather than silently discarded). Also flips the default --log-level from INFO to DEBUG so new debug-level trace points light up without operator action. Event shapes rendered: - assistant text → 'claude> <text>' - assistant tool_use → 'claude tool: <name> <command/file_path/first-arg>' - user tool_result → 'claude tool result (<N chars>)' - system → 'claude system: <subtype>' - result → 'claude result: <first 200 chars>' - error → 'claude error: <msg>' (WARNING level) Makes stalls pinpointable to a specific tool call rather than leaving a silent gap in the log.
rhencke
approved these changes
Apr 15, 2026
This was referenced Apr 15, 2026
FidoCanCode
added a commit
that referenced
this pull request
Apr 15, 2026
Extend the no-tools clause from #517 (reply prompts) to every other prompt that runs through session.prompt(): triage classifier, needs_more_context haiku check, summarize-as-action-item, status nudge, rescope/reorder. Symptom: rhencke/orly PR #32 'Fix CI? <link>' comment got no fido reply for minutes because the triage classifier opened a Bash shell to investigate the linked failing CI run instead of just returning a category. Same pattern across all the other text-only prompts — opus sees a directive-shaped request and decides to do real work, holding the session lock and starving the worker. Adds prompts.NO_TOOLS_CLAUSE constant; prepends it to every classifier/ summarizer/status/rescope prompt.
2 tasks
FidoCanCode
added a commit
that referenced
this pull request
Apr 15, 2026
## Summary - Adds `prompts.NO_TOOLS_CLAUSE` constant and prepends it to every text-only session prompt: `triage_prompt`, `status_system_prompt`, `rescope_prompt`, `needs_more_context`, `_summarize_as_action_item` (both initial + shorten-loop). - Extends the same fix pattern from #517 (which only banned tools in reply prompts) to the rest of the session-nudge prompts. Symptom: rhencke/orly PR #32 "Fix CI? <link>" comment got no fido reply for minutes because the triage classifier fired Bash calls to investigate the linked CI run instead of returning a category. Holds the session lock while opus grinds. ## Test plan - [x] 1844 tests, 100% coverage, ruff + pyright clean (pre-commit hook) - [ ] After merge: triage / status / rescope turns return in seconds, no Bash/Read/Edit calls in the kennel log inside those windows Closes #528. Co-authored-by: Fido Can Code <190991155+FidoCanCode@users.noreply.github.com>
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
Three fixes for the webhook responsiveness regression on PR #515:
Reply-gen disallows tools. Observed 2026-04-14: rhencke's review comment on PR Tastefully colorize kennel status output (closes #511) #515 arrived at 23:36:15, reply posted at 23:41:32 — 4m 44s. The JSONL showed Opus had fired
Bash/Read/Editcalls ontest_status.pyinside the reply-gen turn, treating the review comment as a directive.reply_system_promptnow strictly forbids tool use and explains the worker will do the actual work later from the task queue.Fair preempt yield via
_preempt_pendingevent. Python'sRLockisn't FIFO-fair under contention. A worker thread that released on_cancelcould race back in and re-acquire before the waiting webhook thread got scheduled;iter_events._cancel.clear()at the next turn then wiped the webhook's signal, and the webhook waited a full worker turn to actually run.session.prompt()now sets_preempt_pendingright after_cancel.set(),__enter__clears it on acquire, and the worker's retry loop callssession.wait_for_pending_preempt()between release and re-acquire.More ip-color style coverage. Repo name
BOLD+ state wordGREEN/DIMseparately, Issue/PR/Worker labelsBOLD, tree glyphs and claude pid suffixDIM,kennel: UPinGREENandDOWNinRED_BOLD.Logging added:
session.promptlogs preempt request;wait_for_pending_preemptlogs cede + acquire-latency or timeout warning.Test plan
session: preempter acquired lock after <short>s yieldon webhook preempts