Symptom, cause, and fix for the errors you are most likely to hit with Sonny — install, recipes, providers, the TUI and clipboard, sessions, telemetry, and the mesh.
Every quoted error string below is reproduced from the binary or from the page
that first verified it. If your output differs, trust the binary over this
page and re-check the linked reference. When no row here matches, jump to
Diagnostics
.
-v isn’t wired to anything; only the long form is real.
Use sonny --version or sonny version.
sonny version prints an older version right after make install
Shadow cleaning was skipped — you set NO_SHADOW_CLEAN=1, or staged the build with DESTDIR=, which skips it automatically.
which -a sonny to find the earlier copy and remove it, or rerun make install without NO_SHADOW_CLEAN.
The sonny-mesh systemd unit installed by the deb/rpm/apk package never starts on its own
The postinstall installs the unit but deliberately does not enable or start it — there is no certificate material or authz.yaml yet for it to run safely with.
Provision /etc/sonny/mesh/{node.crt,node.key,ca.crt} and /etc/sonny/mesh/authz.yaml, then systemctl enable --now sonny-mesh.
models.yaml doesn’t reflect edits you expected sonny init to make
sonny init only seedsmodels.yaml the first time; rerunning is a no-op if the file exists, so it never overwrites your edits.
Expected. Edit models.yaml directly, or delete it and rerun sonny init to reseed.
Recipes
Symptom
Cause
Fix
error: [schema_shape] prompts: at least one prompt is required (exit 8, denied_policy)
prompts: was nested under pack: instead of sitting beside it at the document root — the classic copy-paste mistake.
Move prompts: back out to the document root, as a sibling of pack:, not a child.
Error: pack "nope" not found among N discovered packs (exit 1)
--pack <id> named an id that isn’t in the discovered set. This is an exact-match lookup, never fuzzy.
Check ids with sonny recipe list or sonny recipe search, or drop --pack to use the single-primary short-circuit or the picker.
no entry prompt found (expected id="main" or a prompt with type="main")
No prompt has id: main, and none has type: main with a non-empty id either. There is no “first prompt” fallback.
Give exactly one prompt id: main (or type: main plus its own id).
sonny recipe validate fails with wildcard_agent_without_optin / wildcard_mcp_without_optin
You set pack.agents.allowed: ["*"] or pack.mcp.allowed: ["*"] without the matching allow_wildcard: true.
Add pack.agents.allow_wildcard: true or pack.mcp.allow_wildcard: true, or list specific ids. (pack.tools.allowed: ["*"] needs no opt-in.)
unreachable_tool_reference or retired_tool_reference warnings
Advisory only — the recipe still validates. The prompt’s system text names a tool outside that prompt’s tools.allowed, or a retired alias (shell_exec, hosted glab_*/gh_* reachables only via tool_search).
Add the tool to tools.allowed, or rename it in the prompt as the warning suggests (exec instead of shell_exec).
A sub-pack’s tool list silently reverts to the parent’s
You omitted tools: entirely (nil) rather than declaring an empty list. Under the default override inherit mode, nil falls back to the parent; only an explicit allowed: [] denies everything.
Declare tools: {allowed: []} for deny-all, or inherit.tools: merge for the union.
Providers and models
Symptom
Cause
Fix
A profile with uri: "bedrock://…" validates clean, then at runtime drops into the interactive provider prompt (or errors headless)
pack validate doesn’t parse uri: — it’s a Sonny-only field the recipe schema never checks. At load time uri.Parse fails on the unknown scheme and the profile’s provider/model are silently left unset.
There is no bedrock://, vertex:// or openrouter:// scheme. Reach those providers with --provider <name>. Check a profile’s uri: scheme first when it unexpectedly prompts.
An azure:// profile has nowhere to send requests
azure:// is the one API scheme with no default base URL.
Set AZURE_OPENAI_ENDPOINT, or a profile base_url:.
Requests go to the wrong LiteLLM gateway
Several deployments share LITELLM_API_KEY / LITELLM_BASE_URL, so the last environment value wins.
Give each catalog source an env_prefix (e.g. ABS) and populate <PREFIX>_LITELLM_API_KEY / <PREFIX>_LITELLM_BASE_URL.
A recipe profile’s base_url: / api_key_env: are ignored and the wrong account gets billed
--provider (and --model) bypass profile lookup entirely — they’re consulted before the active profile’s own fields.
Don’t pass --provider/-p when the recipe’s own profile should resolve the provider; let its uri:/category: do it.
Anthropic returns HTTP 400 for opus-4-7 / opus-4-8 / opus-5 / sonnet-5 (or the floating opus/sonnet aliases)
Those models reject temperature, top_p and top_k outright.
Remove temp= / top_p= / top_k= from the URI and let the model use its own default.
Downgrading below v0.10.69 is unsupported
Builds newer than v0.10.69 keep each installed catalog version at
<data-dir>/catalogs/<id>/versions/<version>/. The first run of a newer build
migrates automatically, but an older binary does not know about versions/ and
treats every registry catalog as unreadable. If you must run an older binary,
stop every Sonny process, back the directory up, and move the versions back by
hand. Re-upgrading re-runs the migration.
The TUI and clipboard
Symptom
Cause
Fix
clipboard disabled in tui.yaml — nothing copied
Both clipboard.osc52 and clipboard.external are false, so every copy channel is off.
Enable at least one, or run /clipboard-doctor for a tailored recommendation.
reply too large for OSC52 and no other clipboard channel — nothing copied
The reply exceeds clipboard.osc52_max_bytes (default 100000) and clipboard.external is off.
Raise the cap, enable clipboard.external, or copy a shorter selection.
Copy works locally but not over SSH+tmux
tmux’s set-clipboard is off, so tmux never forwards the copy.
set -g set-clipboard on in ~/.tmux.conf — or run /clipboard-doctor, which detects this and prints the line.
tui.yaml: unknown keybinding action "…" at startup
A keybindings: entry names an action the binary doesn’t recognise (a typo, or an action from another version).
Fix the name. Sonny falls back to the default keymap for the whole file, so your other overrides still apply after the fix.
Copying “works” (no error) but the far end never sees anything
You’re SSH’d in without tmux and your local terminal doesn’t support OSC52 — older GNOME Terminal and other VTE-based terminals are known-bad.
Use an OSC52-capable local terminal (iTerm2, kitty, WezTerm, Alacritty, foot, Windows Terminal), or run /clipboard-doctor.
A provider switch says its key is unavailable while ~/.cache/sonny/tui.log shows OpenBao/Vault 403
A long-lived shell still exports an expired BAO_TOKEN / VAULT_TOKEN after the CLI refreshed ~/.bao-token or ~/.vault-token.
Current builds verify the rejected token before trying the CLI token file. If every credential is rejected, re-run bao login / vault login and restart.
Sessions
Symptom
Cause
Fix
--session <uuid>: sql: no rows in result set (exit 2)
--session is deliberately strict: an unknown or malformed UUID is a hard failure, never silently treated as “start new”.
Use a real id (sonny mesh sessions, or the TUI session manager), or drop --session to auto-restore.
sonny run starts a new session instead of resuming the one you expected
Auto-restore only resumes a live session whose recorded working directory is exactly your current directory — not a parent or child. /cd moves it in or out of that match.
Run from the exact directory, or use --continue to resume the most recent session regardless of cwd.
/login succeeded but credentials vanish between machines
Copilot/Hyperbolic OAuth tokens live in sessions.db under your data directory — not the config directory, and not an OS keychain.
Copy or share sessions.db, or just re-run /login there.
Two runs in the same directory see different history
They’re pointed at different databases: --session-db / SONNY_SESSION_DB override the default <data-dir>/sessions.db per invocation.
Confirm both resolve the same file with sonny config path.
Telemetry and tracing
Symptom
Cause
Fix
Every run logs OTel export failures (often 401) though you never configured tracing
A CI runner or shared dev host injected OTEL_EXPORTER_OTLP_ENDPOINT (and possibly other OTEL_*) into the ambient environment; Sonny picks it up whether you asked or not.
Set SONNY_OTEL=0 to disable OTel outright for that run.
context stats / context validate read the wrong or empty cache
Both default to ~/.sonny/trace/cache.db — not an XDG path, unlike everything else Sonny writes.
Pass --store <path> explicitly.
context validate <session> exits non-zero with no obvious error
It asserts four invariants (shell stability, tenant isolation, retention floor ≥ 0.70, return-contract completeness) and fails on the first one violated.
Re-run with --session / --tenant matching what you mean, and read the invariant name in the output.
context pack --strict dropped messages you expected, but loss_categories is empty
loss_categories only populates for the objective/constraints fields this command tracks — not for ordinary recency-window trimming.
Read dropped_messages / dropped_bytes; those are the real signal for a tight --budget.
Mesh
Symptom
Cause
Fix
refusing to start gRPC listener on non-loopback address ":50052" without mTLS: …
--grpc bound a non-loopback address (a bare :50052 counts — it binds every interface) without both--cert/--keyand--ca.
Bind loopback for local work (--grpc 127.0.0.1:50052), or supply full mTLS material.
mesh transport for Synapse listener … refusing plaintext
--synapse-listen is off-loopback without all of --cert, --key, --ca.
Supply complete mTLS material, or bind loopback and explicitly select the development profile.
Synapse peer … must use quic:// (preferred) or wss:// when mTLS is configured
An mTLS node was configured with a plaintext or unsupported peer URL.
Use quic://host:port; wss://host:port/synapse is the compatibility form. The host must match the peer certificate.
authz: permission denied
No /etc/sonny/mesh/authz.yaml — or os.Stat fails for another reason, e.g. an unreadable parent directory — so every RPC is denied by design; or a policy is loaded and simply denies your principal/method.
Check the file exists and its parents are readable, or add a binding/role for your principal.
mesh serve exits at startup with authz policy: …
authz.yaml exists but can’t be read or fails to parse (unknown key, bad YAML, a binding naming an undefined role) — the daemon refuses to start rather than silently denying.
Check the file’s permissions, then validate against the schema (strict keys only). The error names the problem.
authz: no principal in request context
A policy is loaded, but this connection never authenticated a caller — no client certificate, or a cert with no SPIFFE URI SAN.
Provide --client-cert/--client-key/--client-ca, or let auto-discovery find <config dir>/mesh/{user.crt,user.key,ca.crt} with a cert the node’s CA issued.
systemctl reload sonny-mesh doesn’t pick up my change
mesh serve reloads TLS material and authz.yaml on SIGHUP. The log line names only what actually reloaded. nothing to reload means neither is configured; reload on SIGHUP failed means the file has a problem and the previous policy stays in effect.
Read the log line SIGHUP produced. For a principals.d change, or anything else SIGHUP doesn’t cover, use systemctl restart sonny-mesh.
A peer URL is rejected before dialing
Peer URLs must name a supported Synapse transport.
quic://host:port for mTLS; wss://host:port/synapse for compatibility; plaintext ws:// is loopback-development only.
The mesh posture is decided before the dial
Without cert/key/CA material and without the named development profile, you get
refusing plaintext mesh transportinstead of ever finding out the port is
closed. Set SONNY_MESH_INSECURE_DEV_PROFILE to the exact loopback development
profile name if you are deliberately testing plaintext on loopback — never on a
machine that hosts real sessions.
Diagnostics
Run these to work out which of the tables above applies:
Command / command
What it tells you
/clipboard-doctor (TUI)
Diagnoses tmux, SSH, terminal and helper binaries, and prints tailored recommendations.
sonny config path
Every standard path Sonny resolves here: config, data, cache, state, and which project file was picked up.
sonny recipe inspect <id>
A full load-and-resolve pass (unlike validate, which never loads it): baseline scope, signature verification, git provenance.
sonny models list
What wins each model category under the active policy, and every configured (source, model) pair below it. --no-check skips live probing.
sonny mcp list
The resolved MCP server and tool configuration Sonny would actually use.
sonny config show
Effective provider, model, debug and log level — which source won.
sonny doctor
Full environment diagnostics; makes no LLM calls.
journalctl -u sonny-mesh
The daemon’s own log; check here first when it won’t start.
sonny version
Which build you’re actually running (--license, --sbom, --third-party-licenses for compliance questions).