Start with stellar doctor
Run this before debugging anything else:
stellar doctor checks: your CLI version against the latest release, your local Rust toolchain and whether the wasm32v1-none target is installed, whether a wasm optimizer is available, whether an OS secure store (Keychain, Secure Store Service, or *nix keyutils/Secret Service) is available, whether Ledger hardware wallet support is available, whether a container engine (docker or apple-container) is available, your config and data directory locations, the CLI’s XDR schema version, and the reachability plus protocol and RPC version of every network configured in stellar network ls.
A real run looks like this:
stellar doctor does not check account funding, API keys or rate limits, or contract-level health. It is a toolchain and environment diagnostic, not an account or contract diagnostic. A green stellar doctor run does not mean your agent’s key is funded or that a contract call will succeed. For those, see the entries below.
A submit appears to fail, but the funds moved
Read this before you write any retry logic. It is the most important entry on this page.stellar tx send writes an informational line to stderr (ℹ️ Transaction hash is <HASH>; the equivalent line from token transfer reads ℹ️ Signing transaction: <HASH>) and its JSON receipt to stdout, separately:
2>&1, the default habit of most agent tooling, interleaves the two streams and breaks the JSON. A parser sees the stderr line prepended to the JSON object and raises a decode error. An agent that treats that parse failure as a submit failure, and retries, has just resubmitted a transaction that already succeeded. This is a confirmed double-spend, not a hypothetical one.
Fix: never merge stderr into stdout when you intend to parse the result. Redirect stderr separately, and capture it rather than discarding it, since the hash you need to confirm onchain state lives on that stderr line:
Payments and balances
TxInsufficientBalance
Error(Contract, #13), "trustline entry is missing for account"
CODE:ISSUER token, or its Stellar Asset Contract. Two distinct causes produce this same message, and the text does not say which one you have:
- The destination account exists but has no trustline for this asset.
- The destination account does not exist on the network at all. A classic-asset query returns
#13in this case too, so this code alone does not tell you which one you have.
Error(Contract, #6), "account entry is missing"
stellar keys generate <NAME> --network testnet without --fund, so the key is saved locally but the account does not exist onchain. You are querying the wrong network for an account that does exist. Or the account existed and was merged away, which leaves the same code behind: after a successful account-merge, a balance read on the source address returns #6, and that is the cheapest confirmation the merge landed.
Do not confuse this with #13 above. A classic asset reports a missing account as a missing trustline (#13), because trustlines are asset-specific sub-entries of an account. The native asset has no trustline concept, so the same underlying problem, an account that does not exist, reports as #6 instead.
Fix:
Assets and contracts
sac_not_deployed
stellar token commands need the SAC to exist before they can call it.
Fix, the CLI’s own suggested command:
--source-account is required on contract asset deploy. Omitting it fails with a usage error, so if you copy the hint from the error message alone, add an identity before running it.
Messages
unexpected argument '--message' found on stellar message sign
The message is a positional argument, not a flag. There is no --message flag on stellar message sign or stellar message verify.
Fix: drop the flag.
Accounts
A successful account merge prints nothing
tx new account-merge on success exits 0, writes an empty stdout, and emits one stderr line, ℹ️ Signing transaction: <HASH>. There is no success block and no explorer link, unlike every other submit. Silence here is success, not a hang. Confirm it two ways: a balance read on the source address returns Error(Contract, #6), and stellar tx fetch result --hash <HASH> reports account_merge with the swept amount, which is the only place that figure appears.
Account merge refused
ACCOUNT_MERGE_HAS_SUB_ENTRIES: the source account still has subentries such as trustlines, offers, or data entries. Signers do not block a merge and are removed automatically.ACCOUNT_MERGE_IS_SPONSOR: the account’snum_sponsoringis greater than zero. The account is currently sponsoring reserves for other ledger entries.
Networks
Invalid URL Bring Your Own on mainnet
mainnet network entry is a placeholder, not a real RPC endpoint. stellar network ls --long shows it as RPC url: Bring Your Own: <that same docs link>. Any command against mainnet fails this way until you add a real endpoint. testnet, futurenet, and local are unaffected; only mainnet ships without one.
Fix:
https://mainnet.sorobanrpc.com if you want one without a signup.
Default network unreachable
local points at a container that is not running. stellar doctor reports this with a warning glyph rather than failing.
Fix, start the container:
TxBadAuth after signing
stellar tx sign falls back to your saved default network’s passphrase whenever you omit --network or --network-passphrase. If that default does not match the network you submit to, the envelope looks valid but fails on submission with TxBadAuth. Hashing the same unsigned envelope with no network flags and with the default network’s passphrase produces the same hash; hashing it with a different passphrase produces a different one, which is how to confirm this is the cause.
Fix: pass --network explicitly at the sign stage, every time.
Delegated spending
Errors specific to the four patterns in Delegate spending, each measured on testnet.TxBadAuthExtra when co-signing
An envelope carries a signature matching no signer on the account. The usual cause is revoking an agent’s signer weight and then co-signing a proposal the agent had already signed. Stellar rejects the orphaned signature even though your own is correct.
Fix: after revoking, rebuild the transaction and sign it with your key alone. Do not route through tx sign --sign-with-key <AGENT> again.
TxInsufficientFee on a multi-operation transaction
stellar tx op add appends an operation without raising the transaction’s fee, so a sandwich built by piping tx op add is underfunded by default.
Fix: set the fee for the finished transaction on the first command, --inclusion-fee 300 for three operations.
Payment(Underfunded) inside a TxFeeBumpInnerFailed dump
The outer fee bump was charged and the inner payment failed. The usual cause is a zero-XLM agent sending native, because --asset defaults to native and that account holds none by construction.
Fix: pass --asset <CODE:ISSUER> explicitly. A zero-XLM agent spends assets, never XLM.
Error(Contract, #9) means two different things
Read the message, never the code alone.
"not enough allowance to spend"with[remaining, requested]: the draw exceeds what is left."live_until must be >= ledger sequence": anapprovewhose expiration ledger is already in the past, usually from a staleledger latestread.
TxFailed([OpInner(InvokeHostFunction(Trapped))]), a charged fee, and the error in the diagnostic events rather than in any of the strings above.
invalid hex for balance-id
claim-claimable-balance accepts the 64-character hex form only. It rejects the B… strkey that tx send returns and the 72-hex Horizon reports.
Fix: stellar strkey decode <B_STRKEY> | jq -r '.claimable_balance.v0', or drop the first 8 characters of Horizon’s 72.
ClaimClaimableBalance(NoTrust) and (CannotClaim)
NoTrust means the claimant has no trustline for the asset. Add one before claiming; native needs none.
CannotClaim means this claimant’s predicate no longer holds, usually a passed deadline. The fee is still charged. Nothing expires on its own: the entry stays on the ledger consuming a reserve until some claimant takes it, which is why a grant should always name a second, unconditional claimant.
LowReserve on revoke-sponsorship
Buried in a multi-line Rust debug dump, so match on LowReserve alone. A sponsored account with no XLM cannot take over its own reserve, so the sponsorship cannot be unwound this way, for either the account entry or a trustline.
Fix: there is none. With a zero-XLM agent, your control is declining to sign the next fee bump.
Soroban contract calls
Simulation failures
contract invoke and every tx new command run simulation before signing. A failed simulation reports the contract’s own error code (for example Error(Contract, #13) above) rather than a generic CLI failure.
--auth-mode {enforce,root,non-root} controls how strictly the simulation checks Soroban authorization entries. Use --auth-mode enforce (the default) to catch missing or malformed auth before you sign anything. Use --auth-mode non-root while iterating on a contract call whose nested authorization you have not wired up yet, so simulation does not block on it.
Since stellar-cli 28.0.0, the CLI surfaces the diagnostic events attached to a failed onchain transaction, not just a failed simulation. Read the Event log block in the error message from the newest entry down. The first [Diagnostic Event] line is almost always the actual cause.
Environment and secrets
Secrets appearing or not appearing in stellar env
# KEY=<concealed>.
The version history here matters if you are auditing an older install. The concealment machinery existed in 26.0.0, but an inverted check meant secrets could still print in plain text. stellar-cli 27.0.0 fixed that check and added the --reveal flag, which does not exist in 26.0.0. Treat 27.0.0 as the first release where concealment is actually dependable.
To display a concealed value, pass --reveal:
--reveal is passed, so a script checking for the presence of a key by grepping stellar env output must pass --reveal or it will see only the placeholder.
Secure Store does not reveal secret key
--secure-store only supports seed phrases, and the CLI refuses to export the raw secret from one. This surfaces whenever something outside the CLI needs the raw secret string, for example a JavaScript library that takes a private key directly.
Fix: that key cannot be exported. Create a separate, file-backed identity for any flow that needs the raw secret, and keep it out of --secure-store.