Skip to content

hosaka.fm

Errors, limits and replays

A failed tool call is not a transport error: the result is a normal envelope with ok: false, isError: true on the MCP result, and an error object:

{ "type": "rate_limited", "message": "Too many requests this minute for this account.", "recoverable": true, "retryable": true, "data": { "fix_hint": "Pace calls to under the per-minute allowance.", "retry_after_ms": 2000 } }

error.type vocabulary

The vocabulary is closed; every tool description enumerates it and hosaka://capabilities publishes it with a retryable flag per code. "varies" means the value depends on the situation and the row explains how.

error.typerecoverableretryableWhat it means and what to do
invalid_argumentyesnoan argument failed validation; data.param names it, data.fix_hint says how to fix it. Fix and call again
one_of_requiredyesnothe tool needs one of several alternative arguments and got none (for example resolve_artist without q, url, cluster_id, discogs_id or mbid)
range_too_largeyesnolimit or a range exceeds the tool's maximum; data names the maximum. Lower it
placeholder_detectedyesnoan argument looks like a placeholder (<cluster_id>, TODO, string). Resolve the real value first
unauthorizedvariesnono or invalid token on a signed-in tool — recoverable: start the sign-in from _meta["mcp/www_authenticate"]. Not recoverable when the signed-in account is inactive
insufficient_scopereservedreservednot issued today; every signed-in tool uses the same scopes
quota_exceedednonothe account's allowance for the period is used up; data.reset_at (UTC) says when it resets, data.used/data.allowance the figures. Relay that; nothing is purchased inside the assistant
anonymous_capacity_exhaustedyesyesanonymous capacity is used up or paused; sign in for a personal allowance, or retry later
rate_limitedyesyestoo many calls per minute, too many concurrent calls, or too many anonymous calls from one caller; slow down, honour retry_after_ms
linked_capacity_exhaustedyesyeshosaka's shared capacity for signed-in accounts is exhausted for now; retry in a few minutes
request_in_progressyesyesthe identical request is already running (a concurrent duplicate); retry after data.retry_after_ms
upstream_degradedyesyesthe data service is in a degraded window (see below); retry after 30 s or follow next_actions
upstream_errorvariesvariesthe data service answered with an error or a deadline passed. Retryable on a 5xx, an upstream rate limit or a deadline (data.cause); not retryable on a record that is not available. Not recoverable if the service refused hosaka's credentials — report it
metering_unavailableyesyeshosaka could not meter the call so it was not served (fail-closed); retry shortly
tool_disabledyesvariesthe tool is switched off by hosaka for now; data.reason is a short code and data.fix_hint says whether to retry later or sign in
idempotency_conflictreservedreservednot issued today
internalnonoan unexpected server error; report meta.request_id

Cursors (search_catalog)

A cursor is opaque, bound to the signed-in account and to the exact q + filters, and checked before any upstream call. Three things produce invalid_argument with data.param: "cursor": a value this server did not issue (edited, truncated, invented), a cursor replayed from another account, or a cursor carried to a different query. A fourth comes from the data service itself when it no longer accepts a cursor this server did issue (expired). The fix is always the one in data.fix_hint: omit cursor and start the same search again. A cursor longer than 1024 characters fails input validation. Cursors are never issued to anonymous callers (search_catalog requires a signed-in account). A walk can also end early when the data service hands back a cursor this server cannot carry: next_cursor is null and pagination.walk_ended_early explains it (more rows may exist; a narrower query is a new walk).

Deadlines and data.cause

Each upstream call and the tool as a whole run against deadlines. When one passes the error is upstream_error with data.cause:

data.causeMeaning
call_deadlineone upstream call exceeded its per-call deadline (typically a cold dossier); the tool answered honestly instead of waiting
tool_deadlinethe whole tool exceeded its deadline (data.deadline_ms)
client_abortthe client cancelled the request

A deadline-failed call is still metered (meta.units counts the upstream calls made). Retrying a call_deadline after a few seconds often succeeds because the upstream cache is then warm.

The degraded window (upstream_degraded)

If the data service fails repeatedly, hosaka stops calling it for 30 s and answers upstream_degraded immediately, with degraded.recommended_action: "retry_after_30s". Since Sprint 9 the window is also kept per data family: repeated deadlines on one family (for example artist dossiers) open the window for that family only, so resolve_artist keeps working while get_artist is degraded, and a get_artist include whose family is degraded comes back as present: false, reason: "upstream_degraded" with a next_actions entry rather than failing the whole card. Since Sprint 10 a family window also needs several distinct callers: one caller's deadlines can contribute only a minority of the window, so a single client cannot degrade a family for everyone else — if you alone see repeated deadlines, slow your own calls rather than expecting the window to open.

A 404 from the data service for a key you passed is not an error: find_similar_artists, get_upcoming_gigs, get_label and the aura lookup answer ok: true with present: false and reason: "not_found_upstream" plus a next_actions entry (resolve the name again); meta.units is charged as usual.

Budgets a client can see

Idempotency and replays

Identical calls (same account, same tool, same arguments, same view) made within the idempotency window are answered from the stored result with meta.replay: true and are not metered again. A duplicate that arrives while the first is still running waits briefly and then answers request_in_progress with retry_after_ms. If the replay store cannot be reached the call is still served, with meta.idempotency: "unavailable" to say that no replay protection applied. There is no client-supplied idempotency key: the server derives it from the call.

Provenance

Vendored from ono-sendai docs/agents/errors-and-limits.md at commit a447f2b on 2026-10-13. ono-sendai is the source of truth; this page is regenerated from the pinned commit and a test fails if the two disagree. The other pages in this set: connect, tools, envelope, errors and limits, data posture — and the agents landing.