Skip to content

Errors

Find the exact string you saw. Every entry says what produced it and what to do.

The SDKs are fail-open by design: capture problems are logged and the application keeps running. That is the correct trade, and it is also why a misconfiguration can go unnoticed. Read your logs once after wiring Metergraph up.

LanguageWhere messages go
PythonThe metergraph logger, at WARNING. Nothing is printed unless logging is configured
TypeScriptconsole.warn and console.info

Repeated transport failures are rate-limited. The first occurrence of each kind is logged immediately, further ones inside a 60 second window are suppressed, and the next line for that kind carries (N more suppressed in the last 60s). So a single warning can represent a great many failures.

Returned by the Ingest API.

The batch will never succeed. Drop it.

CauseFix
More than 5,000 rows in rowsSplit the batch. POST /v1/traces returns 413 for this instead
rows is missing, empty, or not a listSend at least one row
A row is not an objectEvery entry in rows must be a JSON object
The body is not JSON, or not a JSON objectCheck Content-Type and the serializer
schema_version is not an integer, or is not 1Send 1, or omit it
meta is present but not an objectSend an object, or omit it
Content-Encoding: gzip on a body that is not valid gzipCompress the body, or drop the header
An OTLP body that will not decode in its declared encodingMatch Content-Type to what you actually sent
CauseFix
No Authorization header, or not a Bearer headerSend Authorization: Bearer <token>
Unknown or revoked ingest keyMint a new one in Ingest keys
Expired or invalid mgs_ session tokenRe-exchange at POST /v1/ingest/sessions. The SDKs do this automatically
The workspace is inactive or being purgedContact the workspace owner

quota_exceeded. The monthly captured-call allowance is spent. The response body carries the counter, the allowance and the period.

Capture resumes at the start of the next UTC month, or when the allowance is raised. See Limits and allowances.

The token resolved, but it lacks the scope the endpoint needs. Every ingest endpoint needs ingest.

A dashboard-created key always carries ingest, so a 403 here usually means you are using a read or agent:read key minted by the operator CLI. Mint an ingest key instead.

CauseFix
Body over 8 MiB on the wireSplit the batch in half and retry each half, down to a single row
Body over 8 MiB after gzip decompressionThe same. The limit is checked twice
More than 5,000 GenAI spans on POST /v1/tracesReduce the export batch size in your OTel BatchSpanProcessor
CauseFix
A Content-Encoding other than identity or gzipSend one of those two, or omit the header
A Content-Type on POST /v1/traces that is not application/json or application/x-protobufSet one of those two

Only from POST /v1/ingest/sessions.

CauseFix
protocol_version is not exactly 2Send 2
repository is missing, empty, or not a stringSend owner/name
sdk_version is missing or emptySend your SDK version string
The repository identifier was rejectedMatch owner/name: one slash, no spaces

Only from GET /healthz, when the database check failed. Nothing to do on your side.

Metergraph capture disabled: token and ingest URL are required

Section titled “Metergraph capture disabled: token and ingest URL are required”

No token was found. init() did not mark itself initialized, so a later init() that supplies one will still work.

Fix. Set METERGRAPH_APP_TOKEN in the process that makes the LLM calls. Not your shell, not the build step, not a .env file the runtime never reads. Mint a key in Ingest keys.

Logged once per process.

Metergraph init() was called more than once; the first configuration remains active.

Section titled “Metergraph init() was called more than once; the first configuration remains active.”

A second init() was ignored. Every later call is a no-op.

The usual cause is calling wrap() before your own init(...). wrap() calls init() for you, so by the time your configured init() runs, the environment-only configuration is already locked in.

Fix. Call init(...) first, at the top of your entry point, before anything imports or wraps a client.

Metergraph initialization failed; application is running uninstrumented

Section titled “Metergraph initialization failed; application is running uninstrumented”

Something raised inside init(). The guard caught it, cleared the runtime, and left the process uninstrumented.

The most common cause is a numeric environment variable that is not a number. Python parses METERGRAPH_QUEUE_SIZE, METERGRAPH_BATCH_SIZE, METERGRAPH_FLUSH_SECONDS, METERGRAPH_TEXT_MAX_BYTES, METERGRAPH_CONFIG_POLL_SECONDS and METERGRAPH_CONFIG_HARD_TTL_SECONDS inside this guard, and a value like 5s or 1 MiB raises.

Fix. Check every METERGRAPH_* variable holds a bare number. See Configuration.

Metergraph repository identity is not configured

Section titled “Metergraph repository identity is not configured”

Full text, Python:

Metergraph repository identity is not configured; set
init(repository='owner/repository'), METERGRAPH_REPOSITORY, or provide
.metergraph/config.json. Continuing with legacy ingestion.

Capture still works. What you lose is repository attribution, and the SDK falls back to sending the app token directly to /v1/ingest rather than exchanging it for a short-lived session token.

Fix. Set METERGRAPH_REPOSITORY=owner/name, or pass repository= to init(), or commit a .metergraph/config.json. Logged once per process.

metergraph: found <path> but could not read it

Section titled “metergraph: found <path> but could not read it”

The .metergraph/config.json exists but is unreadable or is not valid JSON.

Fix. Check permissions and the JSON syntax.

metergraph: <path> has an unsupported schema version; ignoring (expected version 2)

Section titled “metergraph: <path> has an unsupported schema version; ignoring (expected version 2)”

The config file declares a version other than 2.

Fix. Set "version": 2, or omit the field entirely.

metergraph: <path> is missing a valid ‘repository’ field; ignoring

Section titled “metergraph: <path> is missing a valid ‘repository’ field; ignoring”

The config file has no repository, or it does not contain a slash.

Fix. Set "repository": "owner/name".

Metergraph found no supported methods on <label> client

Section titled “Metergraph found no supported methods on <label> client”

wrap() ran but recognized nothing to patch. The client is returned unmodified.

CauseFix
Not a supported clientOnly OpenAI, Anthropic and Google clients are supported. See the seam tables for Python and TypeScript
A provider SDK version whose method names movedUpgrade the Metergraph SDK
You wrapped a sub-resource instead of the clientWrap the client object itself, not client.chat

Metergraph patched N seam(s) on <label> client: …

Section titled “Metergraph patched N seam(s) on <label> client: …”

Informational, at INFO. This is what success looks like. (batch-only) means only Batch API result readers were patched, which is correct for a client used solely for batch submission.

Metergraph wrap() failed; client is unmodified and uninstrumented

Section titled “Metergraph wrap() failed; client is unmodified and uninstrumented”

An unexpected error inside wrap(). Capture is off for this client and your application is otherwise unaffected. Python logs the traceback, TypeScript attaches the error object.

Fix. Report it with the traceback and your provider SDK version.

metergraph.wrap() received an unsupported gateway; supported gateways are: openrouter

Section titled “metergraph.wrap() received an unsupported gateway; supported gateways are: openrouter”

Raised, not logged. ValueError in Python, Error in TypeScript, before any provider call. Your value is never echoed back, in case it is a secret.

Fix. The only supported gateway is openrouter. For the Vercel AI Gateway, use provider="vercel" in Python, or vercelAISDKMiddleware() in TypeScript.

metergraph.wrap() gateway requires an OpenAI-compatible provider

Section titled “metergraph.wrap() gateway requires an OpenAI-compatible provider”

You combined gateway= with a provider= other than "openai". A gateway override requires the OpenAI response contract.

Fix. Drop the provider=, or set it to "openai".

metergraph.wrap(gateway=…) requires an OpenAI-compatible client

Section titled “metergraph.wrap(gateway=…) requires an OpenAI-compatible client”

You passed gateway= to a client that detects as Anthropic or Google.

Fix. Use an OpenAI-compatible client for a gateway call.

aiSdkVersion and specificationVersion cannot be combined

Section titled “aiSdkVersion and specificationVersion cannot be combined”

TypeScript only. TypeError from vercelAISDKMiddleware().

Fix. Pass aiSdkVersion: 5 on AI SDK 5, or specificationVersion on AI SDK 6 and 7. Never both.

TypeScript only. TypeError. You called track("name") without a second argument.

Fix. track(fn) or track("stable.name", fn).

modelFor requires options.default to be a non-empty string

Section titled “modelFor requires options.default to be a non-empty string”

TypeScript only. TypeError.

Fix. Pass a non-empty default. This is the one function in the SDK that throws on bad input rather than degrading.

set_session() requires an active Metergraph context

Section titled “set_session() requires an active Metergraph context”

Full text, Python:

metergraph.set_session() requires an active Metergraph context; use
metergraph.context() or metergraph.session().

TypeScript:

Metergraph setSession() requires an active Metergraph context; use
withSession() or withContext().

You called the mutating form outside any Metergraph scope. There was nothing to mutate, so it did nothing.

Fix. Either enter a scope first, or use the scoped form directly.

# Does nothing at module level:
metergraph.set_session(user_id)
# Works:
with metergraph.session(user_id):
...
# Also works: set_session() inside an existing scope
with metergraph.route("checkout.summarize"):
metergraph.set_session(user_id)

Logged once per process.

set_tags() requires an active Metergraph context

Section titled “set_tags() requires an active Metergraph context”

The same rule, naming set_tags() and tags() in Python, setTags() and withTags() in TypeScript.

All of these carry the metergraph: prefix and are rate-limited.

Metergraph authentication failed; capture disabled for this process

Section titled “Metergraph authentication failed; capture disabled for this process”

The ingest endpoint returned 401 or 403 for an app token. Capture stops for the life of the process, because retrying a rejected credential is pointless.

Fix. Check the token. Mint a new key if it was revoked. Restart the process after fixing it: this state is not recoverable in place.

With a session token this message does not appear. The SDK invalidates the session and re-exchanges instead.

ingest rejected batch with HTTP <code> against <url>; dropping this batch (payload-specific, not a process-wide failure)

Section titled “ingest rejected batch with HTTP <code> against <url>; dropping this batch (payload-specific, not a process-wide failure)”

A 400, 404, 413 or 422. The batch is dropped and capture continues.

CodeLikely cause
400A malformed row got into the queue. Usually a custom client
404The ingest URL is wrong. Check METERGRAPH_INGEST_URL for a stray path
413A single row over 4 MiB, after the SDK’s split-and-retry gave up
422A rejected session exchange

ingest request failed with HTTP <code> against <url>

Section titled “ingest request failed with HTTP <code> against <url>”

Any other non-202, usually a 5xx or a 402. The SDK backs off from 1 second, doubling to a 60 second ceiling, and drops rows while it waits.

A 402 here means the monthly allowance is spent. See 402 Payment Required.

ingest request to <url> failed: <Type>: <message>

Section titled “ingest request to <url> failed: <Type>: <message>”

A network-level failure: DNS, TLS, timeout, connection refused.

CauseFix
Wrong or unreachable ingest URLcurl the URL’s /healthz
Egress blocked by a firewall or proxyAllow the ingest host
Timeout under loadThe SDK’s request timeout is 5 seconds. Sustained timeouts mean a network problem, not a Metergraph one

session exchange to <url> failed with HTTP <code>

Section titled “session exchange to <url> failed with HTTP <code>”

The app token could not be exchanged for a session token. Rows are dropped rather than sent with the app token. Backoff doubles from 1 second to 60.

CodeCause
401Unknown or revoked app token
403The key lacks the ingest scope
422The repository string is not owner/name

session exchange to <url> returned no session_token

Section titled “session exchange to <url> returned no session_token”

The endpoint answered but the body had no session_token. Almost always a proxy rewriting the response.

Fix. Check anything sitting between your process and the ingest host.

config poll to <url> failed with HTTP <code>

Section titled “config poll to <url> failed with HTTP <code>”

The GET /v1/config poll failed. Harmless: model_for() returns your default.

Metergraph config authentication failed; using default models

Section titled “Metergraph config authentication failed; using default models”

GET /v1/config returned 401 or 403. The poller stops permanently for this process. Capture is unaffected.

Fix. Nothing is required. If you want the poller back, fix the key and restart.

skipped an eligible GenAI span with no model attribute

Section titled “skipped an eligible GenAI span with no model attribute”

Python OTLP exporter only, rate-limited. A span looked like a GenAI span but carried no model attribute, so no row could be built. The message names the instrumentation scope and span kind and no attribute values.

Fix. Set the model attribute in your instrumentation, or exclude that scope with exclude_scopes. See the exporter reference.

All raised as BatchFirstIneligibleError, before any provider call, so nothing has executed when you see one.

Message fragmentFix
requires accept_duplicate_provider_execution=TruePass it explicitly. A missed deadline runs your request twice and you pay for both
requires a positive deadline_seconds (Python) or requires a positive policy.deadlineMs (TypeScript)Pass a positive finite number
does not support streaming requestsStreaming is direct-only. Drop stream
requires allow_duplicate_tool_call_plans=True for requests with toolsThe two executions may pick different tool plans. Acknowledge it, or drop the tools
has no adapter for provider "..."Supported providers are openai, anthropic and google
request is not eligible for batch-first execution:The adapter’s own reason follows

A submission failure is raised directly rather than triggering the fallback, because no batch was ever created.

Shown on Analysis under Recent attempt.

CodeMeaning
pricing_preflight_failedA production model in scope has no catalog price. The message names the models
launch_failedThe analysis service was unavailable. Contact support
bootstrap_failedThe run could not be set up
export_failedTraffic could not be exported
analyze_failedThe analysis itself failed
import_failedThe report could not be imported
abandonedThe run stopped making progress and was closed

For pricing_preflight_failed, see diagnosing a total that looks too low.

The most common report, and rarely an error message. Work down this list.

  1. Is the token set in the process that makes the calls? Not the shell, not the build step. Unset, the SDK logs one warning and stays silent forever after.

  2. Did the process live long enough to send its queue? Batches leave every 5 seconds by default. Call shutdown() before a short-lived process exits. In serverless code use bindWaitUntil() or wrapHandler(). See Serverless and short-lived jobs.

  3. Is the client actually wrapped? Assign the return value. wrap() returns the client, and throwing it away captures nothing.

    client = metergraph.wrap(OpenAI()) # correct
    metergraph.wrap(OpenAI()) # captures nothing
  4. Are calls going through a path you did not wrap? A second client, a raw HTTP call, a background worker with its own setup, a library that constructs its own client.

  5. Is METERGRAPH_DISABLED set to 1 in that environment? Only the exact string 1 disables the SDK, so check for it precisely.

  6. Is the ingest URL right? Run the curl above. A connection error points at the URL. A 404 points at a stray path on METERGRAPH_INGEST_URL, which must be a base URL with no /v1 suffix.

  7. Is the allowance spent? A 402, and a Capture paused banner in the dashboard.

  8. Has the batch been processed yet? Rows are durable on 202 but appear in the dashboard after the worker runs. Give it a minute before concluding anything.

OpenAI omits usage from a streaming response unless you ask for it. Metergraph adds stream_options.include_usage to streaming chat.completions requests that do not already set it.

Two situations turn that off:

SituationConsequence
METERGRAPH_PATCH_STREAM_USAGE=0Streamed chat calls arrive with no token counts, and therefore no cost
A detected gatewayThe patch is deliberately skipped, because a gateway emits its own usage event

Fix. Unset METERGRAPH_PATCH_STREAM_USAGE, or set stream_options yourself.

Stack attribution is a fallback. A bundler renames functions and rewrites paths, so built TypeScript output frequently attributes to the wrong name or to nothing.

Fix. Use track("stable.name", fn) on every function that calls a wrapped client. In Python, use @metergraph.track where the calling frame sits inside a library rather than your own code.

Two other causes:

  • The calling file is outside app_root. Only frames under that root become attribution frames.
  • The calling file matches a skip pattern: site-packages, metergraph/_capture.py, concurrent/futures, threading.py, or anything you added with skip_frames.

The queue drops rather than blocking your application. That is the correct trade and it does mean a burst can shed rows.

Fix. Raise METERGRAPH_QUEUE_SIZE. Every ingest request reports its own running counts in meta.dropped and meta.transport_errors, so the drop is visible rather than silent.

Route, session and tags live in a context variable. Work handed to a thread pool starts with an empty context.

Fix. In Python, wrap the executor:

from concurrent.futures import ThreadPoolExecutor
pool = metergraph.wrap_executor(ThreadPoolExecutor())

asyncio tasks and async def code need nothing. Neither does TypeScript, which uses AsyncLocalStorage.

See Attribution across threads.

The span inspector reads Request not captured or Response not captured.

CauseFix
METERGRAPH_CAPTURE_TEXT is 0, false, no or offUnset it, or set 1
capture_text=False on init(), or on the enclosing route(), trace() or context()A narrower scope can only turn content off, never back on
You are on the self-hosted OSS serverIt has no content column. Content is discarded structurally

Truncated at the 100 KB field limit is not an error. It is the server-side cap. See Captured fields.