Troubleshooting
When the CLI fails, it fails with a deterministic exit code and a structured error envelope. This page walks through the most common failures, what they mean, and how to recover.
Quick reference: exit codes
| Exit | Cause | Where to start |
|---|---|---|
0 | Success | Nothing to do. |
1 | Generic / uncategorized | Re-run with --verbose to see the underlying error. |
2 | Invalid usage | hamtrax help <command> -- you're missing a flag or have a typo. |
3 | Auth (missing/expired/revoked) | Exit 3 |
4 | Rate limited | Exit 4 |
5 | Tier insufficient | Exit 5 |
6 | QSO cap reached | Exit 6 |
7 | Network / transport | Exit 7 |
8 | Server error (5xx) | Exit 8 |
See AI Agent Guide → Exit-code table for the agent-friendly version with recovery rules.
Exit 3: auth
Three flavors, distinguished by the server error code in the JSON envelope.
unauthorized -- no key configured
hamtrax: whoami: unauthorized: No API key configured.
The CLI couldn't find a key in any of the resolution-order locations. Fix:
hamtrax auth login
# or
export HAMTRAX_API_KEY=htx_live_xxxxxxxxxxxxxxxxxxxxxxxx
See Auth and Keys → Resolution order.
unauthorized -- malformed key
hamtrax: whoami: unauthorized: Missing or malformed Authorization header.
The configured key doesn't start with htx_live_ or has the wrong length. You probably copy-pasted only part of it. Re-copy from the web app reveal modal, or generate a fresh key (the old one is unrecoverable -- keys are shown once).
key_revoked
hamtrax: whoami: key_revoked: API key has been revoked.
The key was revoked in the web app or via auth panic-revoke. The CLI can't un-revoke it -- generate a new key:
- Visit hamtrax.com.
- Open the Hamtrax CLI tool.
- Generate a fresh key.
hamtrax auth set-key htx_live_<new>.
Exit 4: rate limited
hamtrax: contacts list: rate_limited: Too many requests. Please try again in 47s.
Retry-After: 47
You hit one of the per-key or per-IP rate-limit buckets. The buckets and their windows:
| Bucket | Window | Requests | Triggered by |
|---|---|---|---|
cliReadPerKey | 60s | 120 | GETs (whoami, list) |
cliWritePerKey | 60s | 30 | POSTs (create activation, create contact) |
cliDeletePerKey | 60s | 10 | DELETEs (contacts delete) |
cliPerIp | 60s | 200 | All authenticated traffic from one IP |
cliUnauthPerIp | 60s | 20 | Failed auth attempts (anti-grinding) |
Fix: wait Retry-After seconds and retry. The CLI prints the value; an agent should parse it from the error envelope's details.resetSeconds and back off.
# Manual retry pattern
until hamtrax contacts list --folder $FOLDER_ID; do sleep 60; done
If you're seeing cliUnauthPerIp (exit 3 + this error), you're sending failed auth attempts too fast -- the right move is to fix your key, not to retry blindly.
Exit 5: tier insufficient
hamtrax: contacts delete: tier_insufficient: Requires 'elevated' tier.
You tried contacts delete (or another elevated operation) with a basic key. Tiers are fixed at key creation; there's no upgrade path. Fix:
-
Generate a new key at the elevated tier in the web app.
-
Either swap it into the keychain:
hamtrax auth set-key htx_live_<elevated> -
Or scope it to one command via env var (recommended for one-off cleanups):
HAMTRAX_API_KEY=htx_live_<elevated> hamtrax contacts delete qso_92b1...
Then revoke the old basic key if you're done with it. See Examples → Switch tiers.
Exit 6: QSO cap reached
hamtrax: contacts create: qso_cap_reached: Free-tier QSO limit reached. Upgrade to continue logging.
{
"details": { "current": 200, "limit": 200 }
}
You're on the free tier and you've logged 200 native QSOs (web + CLI combined). Imported QSOs don't count -- only ones you logged yourself.
Fix: subscribe at hamtrax.com/account. Once your subscription is active (active or trialing), the cap lifts and the next CLI call goes through. No CLI restart required -- the check is per-request.
If you're a Hamtrax founder (isFounder: true on your user doc), you should never hit this; if you do, file a support ticket.
Exit 7: network
hamtrax: whoami: network error: getaddrinfo ENOTFOUND ...
The CLI couldn't reach the Hamtrax API. Common causes:
- No internet connection. Check
ping 1.1.1.1or your usual probe. - Firewall blocking
*.cloudfunctions.net. Some corporate networks do this. Talk to your network admin or use a different network. - DNS resolver misconfigured. Try
--api-basewith a different region or staging URL if you have one. - TLS interception (mitm). Some enterprise environments rewrite TLS certs. The CLI uses Node's default trust store; if your org installs a custom root CA, set
NODE_EXTRA_CA_CERTSto point at it.
For an agent: retry with exponential backoff up to 3 attempts. If all 3 fail, surface to the user.
Exit 8: server error
hamtrax: contacts create: internal: Internal error.
Something blew up server-side. The error envelope includes a requestId (a UUID) -- include this when filing a support ticket so we can correlate logs.
{
"error": "internal",
"message": "Internal error",
"requestId": "f9c0a1d4-7b3e-4c45-a8d2-12e3a45b6c78"
}
For an agent: retry once after a short delay; if it persists, surface to the user with the requestId.
Debug tips
--verbose flag
Every command accepts --verbose. It dumps the HTTP method, URL, status, headers, and (truncated) request/response bodies to stderr. The configured API key is never included in verbose output.
hamtrax whoami --verbose 2>&1 | head -20
# > GET https://us-central1-ham-radio-app-b818d.cloudfunctions.net/cliApi/v1/whoami
# > Authorization: Bearer htx_live_***************************
# < 200 OK
# < Content-Type: application/json
# < X-RateLimit-Remaining: 119
# < {"callsign":"N0CALL","plan":"free","tier":"basic","nativeQsoCount":12}
_health endpoint
A quick connectivity probe that doesn't consume your rate-limit budget:
curl -s https://us-central1-ham-radio-app-b818d.cloudfunctions.net/cliApi/v1/_health
# {"version":"v1","time":"2026-05-07T15:42:11.000Z"}
If _health is reachable but every other call fails with exit 3, your key is the problem -- not the network.
--api-base for staging
Hamtrax developers can point the CLI at a non-production environment:
hamtrax --api-base https://<staging-host>/cliApi whoami
Keys are environment-scoped -- a production key won't authenticate against staging.
Inspecting the keychain
If you suspect the keychain entry is corrupt:
- macOS: open Keychain Access, search for
hamtrax-cli. - Windows: open Credential Manager, look under Generic Credentials for
hamtrax-cli. - Linux (GNOME):
seahorse(Passwords and Keys), search forhamtrax-cli.
You can delete the entry by hand and re-run hamtrax auth login.
Bypass keychain entirely
For headless / CI debugging:
HAMTRAX_NO_KEYRING=1 HAMTRAX_API_KEY=htx_live_xxx hamtrax whoami
This skips the keychain probe (which can hang on systems without a Secret Service daemon) and the config-file lookup, going straight to the env var.
Common mistakes
- "Why is
mySignot set on my CLI-logged QSOs?" -- It is, but only when you log into an activation folder. If you log into a category or monthly folder, no auto-population happens. Check the folder'sautoFolderTypefield. - "My script created 5 duplicate activation folders." -- The CLI's
activations createis idempotent on(callsign, locationReference, startDate-UTC). If you're seeing duplicates, you're varying one of those fields between calls (different callsigns, references, or running across UTC date boundaries). Pin them and try again. - "
auth panic-revokereturned exit 3." -- The key was already revoked, or it never existed. There's nothing to do. - "
contacts listreturnscursorbut I never asked for one." -- That's how pagination works -- present means there's a next page. Pass it back as--cursorto continue. Absent means you got the last page.
Filing a bug
If you've tried the relevant section above and you're still stuck:
- Run with
--verboseand capture stderr. - Note the
requestIdfrom the error envelope. - Note the CLI version (
hamtrax --version) and Node version (node --version). - File at the Hamtrax-CLI GitHub Issues page.
Do not paste your API key into the bug report -- the CLI redacts it from --verbose output, but if you copy from elsewhere, scrub it first.