Troubleshooting#
This page helps you find out why an agent’s call failed or Lockpin will not run, and what to do about it. It also says what Lockpin does when one of its own parts fails.
Start with lockpin doctor#
lockpin doctor checks the whole chain and prints a pass/fail list with a fix for each failure: the
price catalog and its age, the port, whether a Lockpin answers there, the background service, the
ledger, each provider’s key variable, which agents send their calls through Lockpin and which hooks
they have, and where a test call to each provider would go. It makes no model calls and never
prints a key.
lockpin doctor -config lockpin.json
Behind a company proxy, read its egress line first. Add -network to also send one HEAD request,
with no credentials, to each provider’s host.
If Lockpin runs as a background service, check it and its log:
lockpin service status
System |
Log |
|---|---|
Windows |
|
macOS |
|
Linux |
|
What the agent sees#
Symptom |
Meaning and fix |
|---|---|
|
Another Lockpin (or program) holds the port. Stop it, or pass |
Connection refused from the agent |
Lockpin is not running ( |
|
Team mode: the agent is not sending a Lockpin key ( |
|
Team mode: the key has passed its end date. The error names the command that renews it ( |
|
The task or key reached its limit. The body says which, the numbers and, for key limits, when it resets. Raise it in the meter (tasks: Edit; beyond |
|
The machine reached |
|
The model has no price, so there is no cap. See |
|
A service key’s call did not name its run in |
|
Team mode refuses calls it cannot meter (embeddings, files, batches). |
|
Solo mode refuses a call it cannot meter (embeddings, images, audio, files, batches) until |
|
Team mode: the server has no key for that provider. Set its variable and restart. |
|
|
|
The base URL is wrong. Meta is |
|
The network inspects TLS with a private root the machine does not trust. Give it to Lockpin with |
|
|
|
One of Lockpin’s own parts failed. See If Lockpin itself fails. |
Tip
A held call at the cap waits up to -ask-at-cap (default 3 minutes) for an answer in the meter or
VS Code before it gets its 402. On a server with no screen, list and answer held calls with
lockpin approve list, lockpin approve ID and lockpin deny ID (see
Approvals at the cap).
If Lockpin itself fails#
Lockpin sits between your agent and the provider, so it is one more thing that can fail. It guards
money, so when its own parts fail the default is to fail closed: a call that cannot be reserved and
recorded is refused with a clear status and error code, never sent unseen. -on-fault allow is the
choice to trade that guarantee for availability.
What fails |
|
|
|---|---|---|
The ledger file cannot be written (disk full, file gone) |
|
Calls go on, still capped from memory, marked |
The ledger file cannot be read at start |
Lockpin does not start |
The same: starting empty would reset every cap |
The keys file is unreadable or corrupt when reloaded (team mode) |
|
The old keys keep serving, marked |
Reserving a call fails for a reason other than the budget |
|
The call is forwarded uncapped, marked |
A panic in Lockpin’s own code |
The reservation is released (or charged in full if the call was already sent), the agent gets |
The same, and if the call had not been sent yet, it is forwarded uncapped, marked |
Drift snapshots fail, or drift tracking panics |
The call goes on; drift shows as degraded (after a panic it stays off until restart) |
The same |
Slack or the webhook cannot be reached |
The call goes on; alerts show as degraded |
The same |
allownever turns a working check off. It waives the refusal for the part that is broken, and marks the call, so the spend can be found afterwards.An uncapped call is still recorded: its real cost is added to the task and key when the reply reports usage (or its worst case, when it does not), and the meter’s JSON counts such calls as
uncapped. A model with no price is refused even then.Every refusal or pass-through because of a fault raises a
faultalert (at most one per part every 10 minutes) and a WARN line in the log.A panic after a reply has started cannot become an error, so the connection is cut: your agent sees a broken stream, not a clean end.
Health check#
GET /lockpin/healthz needs no key, even in team mode, and does no real work, so a load balancer
or a supervisor can poll it:
{"status": "degraded", "on_fault": "block", "calls_refused": true,
"components": {"ledger": {"status": "degraded", "reason": "the ledger file cannot be written", "since": "2026-10-20T09:14:02Z"},
"keys": {"status": "ok"}, "handler": {"status": "ok"}}}
status is degraded when any part is. The HTTP status is 503 only when calls are being refused
(block, with the ledger or keys down) and 200 otherwise, so a balancer pulls a server out of
rotation only when it cannot serve; a monitor that wants warnings reads status. The error text,
which can contain file paths, is only included for requests from the server itself.
For a team node in a container, lockpin node health exits 0 when the node answers
/lockpin/healthz with 200, else 1.
When Lockpin is not running at all#
Nothing above helps if the process is gone or unreachable: your agent’s calls fail to connect. Your choices:
Let the agent stop. This is the safe default, and the right one when the cap matters more than uptime. In team mode it is also the only option: developers hold only
lp_...keys and cannot reach the provider without Lockpin.Fall back to the provider in the client. Point the client at the provider’s own URL when Lockpin cannot be reached. Those calls have no cap, no meter and no record, and the real key must be on the client. Fall back only when Lockpin was not reached (connection refused or a short connect timeout), never on a 402, 429 or 503 from Lockpin (those are decisions), and never after a read timeout (the call may be running, and would be sent twice):
import httpx, openai lockpin = openai.OpenAI(base_url="http://127.0.0.1:8787/v1", api_key=KEY, timeout=httpx.Timeout(600, connect=2), max_retries=0) direct = openai.OpenAI(base_url="https://api.meta.ai/v1", api_key=KEY) def respond(**kw): try: return lockpin.responses.create(**kw) except openai.APITimeoutError: raise # it may be running; do not send it twice except openai.APIConnectionError: return direct.responses.create(**kw) # Lockpin was not reached: uncapped!
Keep it up. Run it under a supervisor that restarts it (
lockpin service install,Restart=alwaysin systemd, a Docker restart policy) and point its liveness check at/lockpin/healthz. A restart is quick, and the ledger is replayed from its file.Set timeouts. Give the client a short connect timeout (a second or two) so an outage is noticed at once, and a read timeout longer than your slowest call. Clients that retry on 503 (most SDKs do, twice) ride out a
ledger_unavailablethat clears within seconds, because the refusal carriesRetry-After.
Warning
A call in flight when Lockpin dies was reserved in memory only, so the provider may bill it with nothing in the ledger. A power cut or kernel crash can lose the last events in the ledger (a process crash cannot). One Lockpin process holds the ledger; there is no standby that shares budgets with it.