troubleshooting skill
How the looot plugin's troubleshooting skill reads error codes and outcomes and decides what to do next.
troubleshooting loads automatically when a looot result carries needs_input,
no_supply_for_job, no_runnable_provider, unknown_job, invalid_input, route_capped,
route_no_fit, validation_error, insufficient_balance, idempotency_conflict,
too_many_inflight_runs, forbidden, a 401, or outcome: "miss".
What it does
It reads status, error and outcome before anything else, since a tool call that worked can
still hold a failed run. A tool error has code, message (with the fix in it), retryable and
requestId. A failed run has error with code, message, whoseError (customer,
provider or gateway), retryable and retryHint. For a job: refusal, the exact reason is
in result.code, while error.code reads validation_error.
| Code | What it means | What to do |
|---|---|---|
needs_input |
No provider of the job accepts the input; result.needs lists each provider with the fields it needs. |
Add a named field, using the names from the search answer’s jobInputs, and use a new idempotencyKey. |
no_supply_for_job |
No endpoint does this job. | Search again in other words, or offer capability_request. |
no_runnable_provider |
The job exists but nothing can run for this workspace right now. | Retry later with a new idempotencyKey, or pick another job. |
unknown_job |
The job: id doesn’t exist; the message suggests up to 3 close ids. |
Use a suggested id, or take capability from a search row. |
invalid_input |
The email, url, domain or phone value is malformed. $0 charged. | Fix the value and use a new idempotencyKey. |
route_capped |
Every provider costs more than fallback.maxCostUsd; nobody was called. $0 charged. |
Raise maxCostUsd, after telling the user the price, or drop the cap. |
route_no_fit |
Fallback had providers but none could take the input. $0 charged. | Read route.skipped (needs_identity names the missing field) and add it. |
validation_error |
An argument is wrong; the message names the field. | Fix the named field; never guess argument names. |
insufficient_balance |
Run status: "blocked", with topUp.checkoutUrl and a message. |
Show the link, wait for payment, retry with a new idempotencyKey. |
idempotency_conflict |
This key was already used with a different input or endpoint. | New run, new key. |
too_many_inflight_runs |
Too many runs in flight for this workspace. | Wait 2 seconds and retry with the same key. |
forbidden |
The sign-in or token lacks runs.execute. |
Sign in again and allow running, or create a token with that scope. |
| 401, tools missing | Not signed in, or the sign-in expired. | Use the setup skill. |
Other signs it watches for: status: "completed" with outcome: "miss" (the provider answered
but found nothing, and may still have charged; run the job with fallback so the next provider
tries), outcome: "weak" (a flagged answer, such as a catch-all email or a verdict: "guessed"
pattern guess, worth verifying before use), and status: "queued" or "running" after run
(poll runs_get with the runId; never start a second run for it).
Worked example
Tool result: a run on job:people.email.find comes back
{"status": "failed", "error": {"code": "validation_error"}, "result": {"code": "needs_input", "needs": [{"providerId": "hunter", "needs": ["company"]}]}}.
Agent’s answer: “That provider needed a company name as well as the domain. Let me add it
and try again.” It reruns with company added and a new idempotencyKey.
See money for what’s charged when a run fails, and find-and-run for the full search-and-run loop.