Skip to content

RFC: Re-awakening idle Claude Code sessions — a webhook→injection bridge to consume the dormant push subsystem #122

Description

@evansenter

Note: the original body of this issue was accidentally replaced with the text of RFC #121 at filing time, so its content was lost. This is a reconstruction from the title and surrounding context ("Re-awakening idle Claude Code sessions — a webhook→injection bridge to consume the dormant push subsystem"). Corrections welcome if the original intent differed.

Summary

The bus has a complete push subsystem — register_webhook with channel/event-type filters, HMAC signing, and retries — but nothing consumes it. Meanwhile, the delivery model for events to Claude Code sessions is entirely pull: an idle session doesn't see a DM or a help_needed until its human happens to submit a prompt (the UserPromptSubmit hook drain). This RFC proposes the missing consumer: a small local bridge daemon that subscribes via webhook and injects a wake-up into the idle session, closing the loop from "event published" to "agent acts on it" without a human in the middle.

Motivation

  • DMs today rely on the human-as-router pattern: the server notifies the human via macOS notification, the human switches terminals and types "check the event bus." That works, but only when the human is present and watching.
  • RFC: Real-time event delivery when Claude Code supports MCP notifications #10 (real-time delivery via MCP notifications) is blocked upstream and may stay blocked indefinitely. The bridge sidesteps MCP entirely — it operates at the OS level, so it needs nothing from Claude Code's MCP client.
  • The webhook subsystem already exists and is tested — the marginal cost is one consumer process.

Design sketch

publish_event(channel="session:X", ...)
        │
        ▼
   agent-event-bus ──POST──▶ bridge daemon (localhost)
                                  │  filter: min_level=actionable
                                  ▼
                          injection into session X
  1. On session start (SessionStart hook), the bridge registers a webhook scoped to that session: register_webhook(url="http://127.0.0.1:<port>/wake/<session>", channel="session:<id>", ...) plus optionally the broadcast channels at actionable level only.
  2. On receipt, the bridge decides whether the target session is idle (no in-flight turn) and injects.

Injection mechanism options

  1. tmux send-keys (when sessions run under tmux): the bridge types "check the event bus" into the pane. Crude, robust, visible to the human.
  2. FIFO/spool + hook drain: the bridge appends to a per-session spool file; Stop / UserPromptSubmit hooks already drain the bus and could also drain the spool. This only wakes the session at hook boundaries, so it's "sooner," not "immediate" — but it needs no terminal automation.
  3. claude --resume/headless invocation: the bridge starts a non-interactive turn in the session's context. Most powerful, most invasive; needs care with concurrency and permissions.

Recommendation: start with (1) behind a config flag, keep (2) as the portable fallback.

What this builds on (now available)

Open questions

  1. Idle detection: how does the bridge know a session is mid-turn? Injecting into an active turn is at best noise, at worst interleaves with tool output.
  2. Loop prevention: a woken session may publish events that wake other sessions — is actionable-only filtering sufficient back-pressure, or does the bridge need a cooldown per session?
  3. Scope: does the bridge live in this repo (it's the natural consumer of the bus) or in dotfiles next to the hooks that would cooperate with it?
  4. Security: localhost-only listener + HMAC secret per webhook seems sufficient; anything else needed once Tailscale-remote sessions enter the picture?

Relationship to other issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions