Skip to content

fix(tools): disallow ScheduleWakeup outside /loop mode - #91

Merged
dylanneve1 merged 1 commit into
mainfrom
fix/disallow-schedule-wakeup
Apr 27, 2026
Merged

fix(tools): disallow ScheduleWakeup outside /loop mode#91
dylanneve1 merged 1 commit into
mainfrom
fix/disallow-schedule-wakeup

Conversation

@claudiusthebot

Copy link
Copy Markdown
Collaborator

Summary

Adds ScheduleWakeup to DISALLOWED_TOOLS_CORE, which propagates to chat, dream, and heartbeat in one place. Also adds the first unit test for the DISALLOWED_TOOLS_* lists to lock the behaviour in and document why each entry is there.

Why

ScheduleWakeup is a Claude Agent SDK tool tied to the /loop dynamic-pacing skill. Its description states:

"Schedule when to resume work in /loop dynamic mode — the user invoked /loop without an interval"

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]):

19:31:01  dispatcher  message chat=-1001426819337 started (active=1)
19:31:02  agent       <- (783 chars)
19:39:36  watchdog    No messages processed for 11 minutes
19:41:00  agent       SDK result: contextTokens=173355, numApiCalls=11   ← SDK returned success
19:41:00 → 20:03:00   watchdog fires every 60s up to "35 minutes"        ← lock never released
20:03    bot         (manual restart)

The SDK successfully completed 11 round-trips and returned a usage report, but the expected agent: [chat] -> (Xms, in=…, out=…) completion line and gateway: Context released line 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 dream and heartbeat if the model reached for ScheduleWakeup during memory consolidation or background maintenance. Putting it in DISALLOWED_TOOLS_CORE covers 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:

Blacklist (current) Whitelist
New SDK tools Available by default Hidden by default
Maintenance Reactive (block bad ones) Proactive (review every SDK upgrade)
Risk New buggy tools can hang us before we notice Forgetting a tool silently breaks features

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 pass
  • npm test — full suite 1394/1394 passing (was 1388 baseline + 6 new)
  • npm run typecheck — clean
  • npm run lint — no new warnings (9 pre-existing, all unrelated)
  • npm run format:check — clean

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

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.
@dylanneve1
dylanneve1 requested a review from Copilot April 27, 2026 20:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ScheduleWakeup to DISALLOWED_TOOLS_CORE with an explanatory comment about the dispatcher hang.
  • Introduce a new Vitest suite validating DISALLOWED_TOOLS_CORE, and that DISALLOWED_TOOLS_BACKGROUND and DISALLOWED_TOOLS_CHAT inherit 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.

@dylanneve1
dylanneve1 merged commit 75b15f0 into main Apr 27, 2026
17 checks passed
@dylanneve1
dylanneve1 deleted the fix/disallow-schedule-wakeup branch April 27, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants