Claude Code mod: ask before a paid Sume call (and claude -p)

A Claude Code mod can hold a paid Sume MCP call with $.ui.ask and show max_spend_usd in the question. In claude -p nobody answers, so it refuses. Code inside.

5 min readSume
All posts

Yes: a Claude Code mod can pause a paid Sume call and ask you first. A tool.call hook can await before it calls next, and $.ui.ask(question, options) puts your question in the dialog Claude uses to ask you something and resolves to the label you pick. The reason to use it over a plain permission prompt is that your question can carry the arguments you care about, such as Sume's max_spend_usd. In a claude -p run nobody can answer, $.ui.ask rejects, and a hook written the safe way refuses the call.

This follows Claude Code's events guide, which has a worked example that holds risky Bash commands, read 2026-10-03. The version below applies the same shape to Sume's paid tools.

What does the hook look like?

It matches the paid tools by name, lets dry_run=true calls through because Sume documents that flag as an admission and cost preview that does not submit the job, and asks about everything else. The tool names are from Sume's tools and gates page and assume the server was added under the name sume, so Claude Code names them mcp__sume__....

const PAID = /^mcp__sume__(generate_image|generate_video|music_create|tts_create|avatars_create|avatar-videos_create)$/

export function register(on) {
  on('tool.call', { tool: PAID }, async ($, e, next) => {
    // A dry run submits nothing, so let it through
    if (e.dry_run === true) return next(e)
    let answer = 'Refuse'
    try {
      answer = await $.ui.ask(
        'Run paid Sume call ' + e.tool + '? max_spend_usd: ' + (e.max_spend_usd ?? 'not set'),
        ['Run it', 'Refuse']
      )
    } catch {
      // dismissed, or claude -p with nobody to ask: stay on Refuse
    }
    if (answer !== 'Run it') {
      return { deny: 'The user declined this paid Sume call. Run it with dry_run=true instead.' }
    }
    return next(e)
  })
}

What happens in each situation?

The hook has five outcomes. Claude Code's guide lists the first four for its Bash example, and the headless case follows from the same rejection. In every case except a deliberate Run it, the call does not go ahead and Claude reads the deny text instead.

Outcomes of the hold, from Claude Code's events guide, read 2026-10-03.
SituationWhat the hook getsWhat Claude reads
You pick Run itanswer is Run it, so the hook calls next(e) and the normal permission check still runs.The tool's real result
You pick Refuseanswer is Refuse.The deny text
You type something else$.ui.ask resolves to the typed text, which is not Run it.The deny text
You dismiss the question or pick Chat about this$.ui.ask rejects; the catch leaves answer on Refuse.The deny text
claude -p, no one to ask$.ui.ask rejects the same way.The deny text

Why does a headless run end in a deny?

Because the default answer is Refuse. Claude Code's guide says to start from the safe answer so that a question nobody answers refuses the command. That is the right behavior for paid work: a CI job that was never meant to spend money cannot spend it through a forgotten prompt. The deny text above tells Claude what to do instead, which here is a dry_run=true call that returns the admission preview without submitting.

If you do want unattended spending in CI, do not loosen this mod. Use a separate configuration with an API key, a spend ceiling the run cannot raise, and job recovery by id. Sume's Agent Completions require generation_spend_cap_usd on every call for that reason; a hosted MCP session has no such required field.

What does it not replace?

A question in the terminal is a human check, nothing more. Sume's own gates are separate, and each of them still applies to a call that you approve.

  • A wallet. $.ui.ask is a human check, not a limit; Sume's spend gate is wallet and admission, and max_spend_usd is enforced only when the call includes it.
  • A scope. With OAuth and Write off, Sume's paid tools are hidden and return insufficient_scope, so the question never appears (OAuth and API keys).
  • An idempotency_key. Sume requires one on every paid or write call; it dedupes a retry and is not human approval.
  • The time limit. Claude Code says time spent inside a mods API call such as $.ui.ask does not count against the hook's limit, but time awaiting a promise of your own does, and a hook that times out is skipped, so the held call would run.

Should I use this or an ask rule?

Use a permission ask rule when a prompt is enough: it takes no code, and it is what the ask-rule post sets up for Sume's paid tools. Use the mod when the question should show a field, when dry_run calls should skip the prompt, or when you want the refusal text written for the model. Claude Code notes that a mod cannot restyle the permission prompt itself, so the mod's question and the built-in prompt can both appear; the usual permission check still runs after you pick Run it.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume