Claude Code mod button calling $.prompt.submit doesn't work the first time (where to call it) A developer found that calling `$.prompt.submit` inside a Claude Code mod button's `onPress` handler or a command hook silently fails to start a turn in versions 2.1.286 to 2.1.288, with the call discarded when the event ends and no error raised. The workaround is to have the button only store the text in a variable and dispatch it from a `$.clock.every` clock created in `session.start`, which also keeps `$.process.run` work alive past the event; the developer notes the root cause is unverified and that per-feature clocks can multiply on plugin reload when `$.config.set` triggers re-registration. If you call $.prompt.submit inside a mod button's onPress or inside a command, Claude sometimes doesn't start. In the versions I tested, a $.prompt.submit called inside the press event was thrown away when the event ended, with no error . Have the button only store what to send in a variable, and send it from a clock created in session.start . That works reliably. Versions checked: Claude Code 2.1.286 to 2.1.288 October 2026, desktop Code tab . Put a button in a mod's panel, and when pressed, send a request to Claude. It's the first thing you want to build. js t.Button { key: 'ask', label: 'Decide now', onPress: async = { await $.prompt.submit { text: 'Check the market and decide' } }, } Pressing it doesn't start Claude. Sometimes pressing twice works, which made the cause slow to find. Sending from inside a command /xxx behaved the same. On the official API page, $.prompt.submit sits in the section about starting a turn from background work . Three key points: $.prompt.submit { text } waits until the session is idle, then starts a new turn await it inside a handler that runs while Claude is working asUser: true , it's sent as the person's own words without that line The example given also sends from inside a clock $.clock.every created in session.start . The official text goes as far as "it waits, so don't await it". It does not say that a call inside an event gets thrown away. What follows is what I saw in my versions. $.prompt.submit called inside a button's onPress or a command hook disappeared when the event ended, and no turn started .catch Why it gets dropped is not verified . But together with the fact that the official example sends from a clock, I believe the right place to start a turn is a clock, not an event. The button only puts "what to send" into a variable, and a 1-second clock finds it and sends it. js let toSend = null // Button: just queue it onPress: = { toSend = 'Check the market and decide' $.ui.toast 'Sending...' } // Create the clock once in session.start on 'session.start', async $, e, next = { $.clock.every 1 000, = frame $ return next e } async function frame $ { if toSend return const text = toSend toSend = null // clear before sending so the next tick doesn't send it twice await $.prompt.submit { text } } There's up to a 1-second delay between pressing and sending, but you barely notice it. Show "Sending..." in a toast or the panel right after the press so there's no doubt whether the press registered. frame takes $ , so put it at the top level of the same file . If you pass $ to a function created inside a hook or to a function in another file, the static check fails and the whole mod doesn't load part 5 . Work that starts an external process with $.process.run opening a file in the default app, playing a sound was also sometimes killed when the event ended if started inside the event. Route that to the same clock too. Processes started from the clock keep running beyond the event. Remember "any work that outlives a single event starts from the session.start clock" and you won't get lost. The official docs also describe running work that spans more than one event on a clock started in session.start . If you create a clock per feature, you hit a different trap. In my versions, writing settings $.config.set reloaded the plugin and ran register again, so clocks multiplied and the same work ran twice . The official explanation says that on reload the previous clocks stop and the new load creates its own. It may be fixed in newer versions, but which version changed it is not verified . To be safe either way, I follow two rules: js let timer = null function startClock $ { try { timer?.cancel?. } catch { / do nothing if there isn't one / } timer = $.clock.every 1 000, = frame $ } The clocks returned by $.clock.every and $.clock.after have cancel official . There was one more cause of "I pressed it but nothing happened". If you re-render the panel every second, the button can be recreated mid-press. Splitting it like this fixed it. Calling $.ui.invalidate 'ui.render' only when a value changes cuts wasted re-renders in the first place. Text sent with $.prompt.submit looks to Claude like "a request that came from a mod". asUser . Claude reads it knowing it came from a mod A mod that asks Claude something with one button can also start turns without the person knowing. From the installer's side, where a mod calls $.prompt.submit from is one of the things worth checking part 10 . | Symptom | Cause | Fix | |---|---|---| | Asking from a button or command doesn't start Claude | $.prompt.submit inside the event was dropped in my versions | Queue in a variable, send from the session.start clock | | Opened apps or sounds get cut off | Processes are killed when the event ends | Start them from the clock as well | | The same work runs twice | Clocks are created in more than one place | One clock, stop it before creating a new one | | The press feels registered but nothing happens | Re-rendering recreated the button | Re-render every 5 seconds | You can read real mods that ask Claude from a button, with their code, in the modscode reviewed gallery https://modscode.com/gallery/ . Comparing how they create clocks and place buttons shows the patterns clearly. This article was written with AI assistance Claude and checked against the official Claude Code docs; anything marked "not verified" could not be confirmed there.