SLOPSHOPPER

needs-input-notify

Notifies you when Claude ends a turn with a "needs input:" line

newstatusprompt
★ 1v0.1.0MITupdated 2026-10-21roeibh/claude-mods/plugins/needs-input-notify
A shopper browsing a rack in a slop shop
README

<h1 align="center">needs-input-notify</h1>

<img alt="Claude Code mod" src="https://img.shields.io/badge/Claude_Code-mod-D97757"> <img alt="Claude Code 2.1.287+" src="https://img.shields.io/badge/Claude_Code-2.1.287%2B-3a3a3a">


Five sessions are running. One finishes its part and ends with a question for you. You notice forty minutes later.

Claude ends a blocked turn with a needs input: line. This mod sends that line to you as a push:

⏺ Both migrations are written. The second one drops a column.

  needs input: OK to drop users.legacy_role in prod?

────────────────────────────────────────────────────────
📱 my-app: OK to drop users.legacy_role in prod?

Install

/plugin install needs-input-notify --marketplace roeibh/claude-mods

Type y to add the marketplace, then pick a scope. From a shell it's claude plugin install with the same arguments. In the desktop app, add the marketplace roeibh/claude-mods from + → Plugins.

How it works

The push goes through Claude Code's own notifications, so it reaches your phone and desktop even when the session runs in the background. If you're already looking at that session, Claude Code holds it back. When a push can't reach anyone, your terminal's notification fires instead.

Until you reply, the status line under the prompt reads waiting on you. Subagent turns are ignored, since only the main session can be waiting on you.

<sub>Develop with claude plugin validate . · claude plugin test . · claude --plugin-dir .</sub>

Source 1 files
hooks/register.ts 47 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3// "needs input: <ask>" on its own line; tolerates markdown around the label.
4const NEEDS_INPUT = /^[\s>*_`]*needs input:[*_`]*[ \t]*(.*)$/im
5const PUSH_LIMIT = 200
6
7const projectOf = async ($: EngineInterface): Promise<string> => (await $.session.cwd()).split('/').filter(Boolean).pop() || 'claude'
8
9// Claude Code's own push reaches your phone and desktop, even from a background session.
10const tryPush = async ($: EngineInterface, message: string): Promise<boolean> => {
11  try {
12    const pushed = await $.tool.call({ tool: 'PushNotification', message, status: 'proactive' })
13    if (pushed.deny !== undefined || pushed.isError) {
14      return false
15    }
16    const { pushSent, localSent, disabledReason } = pushed.result
17    return Boolean(pushSent || localSent) || disabledReason === 'user_present'
18  } catch {
19    return false
20  }
21}
22
23const notify = async ($: EngineInterface, ask: string): Promise<void> => {
24  const project = await projectOf($)
25  if (await tryPush($, `${project}: ${ask}`.slice(0, PUSH_LIMIT))) {
26    return
27  }
28  await $.ui.notify(ask, { title: `needs input · ${project}` })
29}
30
31export const register: Register = on => {
32  on('turn.complete', async ($, e, next) => {
33    const ran = await next(e)
34    const match = e.agentId === undefined ? NEEDS_INPUT.exec(e.answer) : null
35    if (match) {
36      $.ui.status('waiting on you')
37      await notify($, match[1]?.trim() || 'Claude is waiting on you')
38    }
39    return ran
40  })
41
42  on('prompt.submit', ($, e, next) => {
43    $.ui.status(undefined)
44    return next(e)
45  })
46}
47