---
title: troubleshooting skill
description: How the looot plugin's troubleshooting skill reads error codes and outcomes and decides what to do next.
sidebar:
  label: Troubleshooting
---

`troubleshooting` loads automatically when a looot result carries [`needs_input`](/errors/job-refusals#needs_input),
[`no_supply_for_job`](/errors/job-refusals#no_supply_for_job), [`no_runnable_provider`](/errors/job-refusals#no_runnable_provider), [`unknown_job`](/errors/job-refusals#unknown_job), [`invalid_input`](/errors/job-refusals#invalid_input), [`route_capped`](/errors/job-refusals#route_capped),
[`route_no_fit`](/errors/job-refusals#route_no_fit), [`validation_error`](/errors/rest-errors#validation_error), [`insufficient_balance`](/errors/rest-errors#insufficient_balance), [`idempotency_conflict`](/errors/rest-errors#idempotency_conflict),
[`too_many_inflight_runs`](/errors/rest-errors#too_many_inflight_runs), [`forbidden`](/errors/rest-errors#forbidden), a 401, or `outcome: "miss"`.

## What it does

It reads `status`, `error` and [`outcome`](/concepts/outcomes) 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`](/concepts/job-inputs), and use a new [`idempotencyKey`](/concepts/idempotency). |
| `no_supply_for_job` | No endpoint does this job. | Search again in other words, or offer [`capability_request`](/mcp-tools/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`](/money/cost-cap), 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](/plugin/skills/setup). |

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`](/concepts/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`](/mcp-tools/run)
(poll [`runs_get`](/mcp-tools/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](/plugin/skills/money) for what's charged when a run fails, and
[find-and-run](/plugin/skills/find-and-run) for the full search-and-run loop.

<Related />
