{"slug": "claude-code-mod-button-calling-prompt-submit-doesn-t-work-the-first-time-where", "title": "Claude Code mod button calling $.prompt.submit doesn't work the first time (where to call it)", "summary": "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.", "body_md": "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.\n\nVersions checked: Claude Code 2.1.286 to 2.1.288 (October 2026, desktop Code tab).\n\nPut a button in a mod's panel, and when pressed, send a request to Claude. It's the first thing you want to build.\n\n``` js\nt.Button({\n  key: 'ask',\n  label: 'Decide now',\n  onPress: async () => { await $.prompt.submit({ text: 'Check the market and decide' }) },\n})\n```\n\n**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.\n\nOn the official API page, `$.prompt.submit` sits in the section about **starting a turn from background work**. Three key points:\n\n`$.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\nThe example given also sends from inside a clock (`$.clock.every`) created in `session.start`.\n\nThe 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.\n\n`$.prompt.submit` called inside a button's `onPress` or a command hook disappeared when the event ended, and no turn started`.catch`\nWhy 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.\n\nThe button only puts \"what to send\" into a variable, and a 1-second clock finds it and sends it.\n\n``` js\nlet toSend = null\n\n// Button: just queue it\nonPress: () => {\n  toSend = 'Check the market and decide'\n  $.ui.toast('Sending...')\n}\n\n// Create the clock once in session.start\non('session.start', async ($, e, next) => {\n  $.clock.every(1_000, () => frame($))\n  return next(e)\n})\n\nasync function frame($) {\n  if (!toSend) return\n  const text = toSend\n  toSend = null            // clear before sending (so the next tick doesn't send it twice)\n  await $.prompt.submit({ text })\n}\n```\n\nThere'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.\n\n`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).\n\nWork 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.\n\nRemember \"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`.\n\nIf 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**.\n\nThe 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:\n\n``` js\nlet timer = null\n\nfunction startClock($) {\n  try { timer?.cancel?.() } catch { /* do nothing if there isn't one */ }\n  timer = $.clock.every(1_000, () => frame($))\n}\n```\n\nThe clocks returned by `$.clock.every` and `$.clock.after` have `cancel()` (official).\n\nThere 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.\n\nSplitting it like this fixed it. Calling `$.ui.invalidate('ui.render')` only when a value changes cuts wasted re-renders in the first place.\n\nText sent with `$.prompt.submit` looks to Claude like \"a request that came from a mod\".\n\n`asUser`. Claude reads it knowing it came from a mod\nA 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).\n\n| Symptom | Cause | Fix | \n|---|---|---|\n| 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 | \n| Opened apps or sounds get cut off | Processes are killed when the event ends | Start them from the clock as well | \n| The same work runs twice | Clocks are created in more than one place | One clock, stop it before creating a new one | \n| The press feels registered but nothing happens | Re-rendering recreated the button | Re-render every 5 seconds | \n\nYou 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.\n\n*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.*", "url": "https://wpnews.pro/news/claude-code-mod-button-calling-prompt-submit-doesn-t-work-the-first-time-where", "canonical_source": "https://dev.to/nakadadev/claude-code-mod-button-calling-promptsubmit-doesnt-work-the-first-time-where-to-call-it-158i", "published_at": "2026-10-11 13:09:41+00:00", "updated_at": "2026-10-11 13:21:51.881300+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents"], "entities": ["Claude Code", "Anthropic"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/claude-code-mod-button-calling-prompt-submit-doesn-t-work-the-first-time-where", "markdown": "https://wpnews.pro/news/claude-code-mod-button-calling-prompt-submit-doesn-t-work-the-first-time-where.md", "text": "https://wpnews.pro/news/claude-code-mod-button-calling-prompt-submit-doesn-t-work-the-first-time-where.txt", "jsonld": "https://wpnews.pro/news/claude-code-mod-button-calling-prompt-submit-doesn-t-work-the-first-time-where.jsonld"}}