fix(tools): disallow ScheduleWakeup outside /loop mode - #91
Merged
Conversation
ScheduleWakeup is a tool tied to the /loop dynamic-pacing skill. When called outside that skill, the runtime registers a wakeup that never fires — leaving the agent's turn ended mid-dispatch with no completion signal, which wedges the per-chat dispatcher lock indefinitely. Adding ScheduleWakeup to DISALLOWED_TOOLS_CORE blocks it for chat, dream, and heartbeat in one shot. Root cause of a 35-minute hang on 2026-04-27 (talon.log [e2589f7e]): the SDK reported success at 19:41 but the dispatcher never released the chat lock. Watchdog fired "No messages processed for X minutes" every minute up to 35 before manual restart. Adds first unit test for the DISALLOWED_TOOLS_* lists to lock the behaviour in and document why each entry exists.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR prevents a Claude Agent SDK /loop-specific tool (ScheduleWakeup) from being callable in Talon’s normal execution contexts by adding it to the shared disallowed-tools list, and adds unit tests to lock in the intended inheritance/coverage of the disallowed tool lists.
Changes:
- Add
ScheduleWakeuptoDISALLOWED_TOOLS_COREwith an explanatory comment about the dispatcher hang. - Introduce a new Vitest suite validating
DISALLOWED_TOOLS_CORE, and thatDISALLOWED_TOOLS_BACKGROUNDandDISALLOWED_TOOLS_CHATinherit CORE (plus their extra entries).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/core/constants.ts |
Adds ScheduleWakeup to the backend-agnostic core disallowed tool list so it propagates to chat/background contexts. |
src/__tests__/disallowed-tools.test.ts |
Adds tests asserting CORE contents and inheritance rules for CHAT/BACKGROUND disallowed tool lists. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
6 tasks
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
Adds
ScheduleWakeuptoDISALLOWED_TOOLS_CORE, which propagates to chat, dream, and heartbeat in one place. Also adds the first unit test for theDISALLOWED_TOOLS_*lists to lock the behaviour in and document why each entry is there.Why
ScheduleWakeupis a Claude Agent SDK tool tied to the/loopdynamic-pacing skill. Its description states:When called outside
/loop, the runtime registers a wakeup that never fires — the agent's turn ends mid-dispatch with no completion signal, and the per-chat dispatcher lock is held until manual restart.Confirmed root cause of a 35-minute hang on 2026-04-27
From
talon.log(PID 534026, dispatcher[e2589f7e]):The SDK successfully completed 11 round-trips and returned a usage report, but the expected
agent: [chat] -> (Xms, in=…, out=…)completion line andgateway: Context releasedline were never logged. A parallel cron job ran fine in another chat at 20:00, so the harness as a whole was alive — only this one dispatcher was wedged.The agent had called
ScheduleWakeup(delaySeconds=90, …)near the end of the turn while waiting for a GitHub Pages deploy. That ended the turn early before the final user message could be sent, which appears to bypass the dispatcher's lock-release path.Why CORE not just CHAT
The same hang would happen identically in
dreamandheartbeatif the model reached forScheduleWakeupduring memory consolidation or background maintenance. Putting it inDISALLOWED_TOOLS_COREcovers all three execution contexts (chat / dream / heartbeat) with one entry, matching how the rest of the planning/interactive tools are handled.What about a whitelist?
Worth considering as a follow-up but I'd keep it out of this PR. Trade-offs as I see them:
For Talon's posture (long-running, public-facing, deterministic behavior matters) a whitelist is probably the right call long-term — but it's a bigger change that needs a careful audit of every SDK tool we currently rely on (Read, Write, Edit, Bash, Glob, Grep, NotebookEdit, ListMcpResourcesTool, ReadMcpResourceTool, Agent, …). Happy to open a separate PR for that if you want.
In the meantime there are a few other recently-added Anthropic-native tools the bot was offered today that aren't currently blocked and might be worth reviewing in a follow-up:
CronCreate,CronDelete,CronList— Talon has its own cron system via the telegram plugin; the SDK-native ones could conflict.RemoteTrigger,PushNotification— unclear semantics, probably duplicate functionality Talon already has.Monitor— actually useful for streaming-event watches, probably keep.Not blocking those in this PR; flagging for the conversation.
Test plan
npx vitest run src/__tests__/disallowed-tools.test.ts— 6 new tests passnpm test— full suite 1394/1394 passing (was 1388 baseline + 6 new)npm run typecheck— cleannpm run lint— no new warnings (9 pre-existing, all unrelated)npm run format:check— cleanCo-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com