> ## Documentation Index
> Fetch the complete documentation index at: https://midplane.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Fixes for the most common Midplane issues — denied queries, agent connection problems, and boot failures.

The fastest way to diagnose a Midplane issue is to read the denial reason or the boot log — both name what's wrong and usually what to change. This page collects the issues people hit most, with the fix for each.

## Denied queries

A denial is usually correct: Midplane is doing its job. But if a query you expected to succeed was denied, the reason string names the rule and the table. Match it to one of these.

<AccordionGroup>
  <Accordion title="My SELECT was denied" icon="ban">
    The likely cause: **the table is set to `deny`.** A table marked `deny` allows neither reads nor writes. Check your policy — if the table's access is `deny` (or your `default` is `deny` and the table isn't listed), reads are blocked. Set it to `read` (or `read_write`) to allow SELECTs. See [table access](/docs/concepts/table-access).
  </Accordion>

  <Accordion title="My write was denied" icon="trash">
    Writes deny by default. To allow a write, mark the table `read_write` in your policy:

    <Tabs>
      <Tab title="Cloud (dashboard)">
        Open the database's policy grid and set a per-table override to `read_write`. Cloud hot-reloads the change.
      </Tab>

      <Tab title="Self-host (YAML)">
        ```yaml theme={null}
        table_access:
          default: read
          tables:
            feature_flags: read_write
        ```

        Restart the container (or hot-swap via `POST /admin/policy`) so the change takes effect. Validate first with `midplane policy lint` / `validate`.
      </Tab>
    </Tabs>

    Note that `read_write` grants ordinary DML only. Side-effecting statements — `COPY`, `LOCK`, `CALL`, `NOTIFY`, `SET` — deny regardless of YAML, because the policy can't grant a per-table target for them.
  </Accordion>
</AccordionGroup>

## Agent connection problems

<AccordionGroup>
  <Accordion title="Claude Code can't connect" icon="plug">
    The most common cause is a missing transport flag. Claude Code defaults to stdio and will treat your URL as a launcher command, failing silently. Add `--transport http`:

    ```bash theme={null}
    claude mcp add --transport http midplane http://localhost:3000/mcp
    ```

    Verify it registered with `claude mcp list`. See [Claude Code setup](/docs/agents/overview).
  </Accordion>

  <Accordion title="Claude Desktop rejects my localhost URL" icon="laptop">
    Claude Desktop's Custom Connectors UI enforces HTTPS and rejects `http://localhost` (and won't accept a self-signed cert against localhost). For a local self-host instance, use the `mcp-remote` shim in the config file instead:

    ```json theme={null}
    {
      "mcpServers": {
        "midplane": {
          "command": "npx",
          "args": ["-y", "mcp-remote", "http://localhost:3000/mcp"]
        }
      }
    }
    ```

    This requires `npx` (Node.js) on your `PATH`. Restart Claude Desktop after editing the file. Hosted Midplane (an HTTPS endpoint) uses Custom Connectors normally. See [Claude Desktop setup](/docs/agents/overview).
  </Accordion>
</AccordionGroup>

## Connecting to your Postgres

<AccordionGroup>
  <Accordion title="Agent query fails with 'self signed certificate'" icon="lock">
    Your database presents a **self-signed or private-CA TLS certificate** and its DSN uses `sslmode=require` (or `prefer`). Since engine `0.15.0` Midplane follows libpq's TLS semantics, where `require` **encrypts without verifying** the certificate — so a self-signed cert connects. On an older engine, `require` was upgraded to full verification and rejected the cert with a bare `self signed certificate` the moment an agent ran its first query.

    * **Encryption is enough** (a self-signed or private cert) — `sslmode=require` connects on `0.15.0+`; upgrade the engine if you're behind.
    * **You want the cert verified** — add `sslrootcert=/path/to/ca.crt` to the DSN and Midplane verifies against that CA (like `verify-ca`). `verify-ca` / `verify-full` are unchanged.
    * **Encryption isn't needed** (a private, trusted network) — `sslmode=disable` turns TLS off.

    A DSN with no `sslmode` behaves exactly as before. On Cloud the managed engine already runs `0.15.0+`, so this applies to whichever database you connect.
  </Accordion>
</AccordionGroup>

## Boot failures

<AccordionGroup>
  <Accordion title="Boot fails with an unset ${VAR}" icon="circle-x">
    Midplane interpolates `${ENV_VAR}` references in `databases[].url` so DSNs stay out of your YAML. If a referenced variable isn't set in the environment, the URL can't resolve and the server fails to boot.

    Check that every variable named in your policy file is present in your `--env-file` (or compose environment). For example, a policy with `url: ${PROD_DATABASE_URL}` needs `PROD_DATABASE_URL` defined.

    You can structurally validate a policy offline without the variables set — `midplane policy validate <file>` doesn't fail on an unset `${VAR}`, so it checks the YAML shape independent of the environment. The variable only has to resolve at server boot.
  </Accordion>
</AccordionGroup>

<Note>
  When in doubt, read the audit log. `midplane audit tail` streams every attempt and decision, including the rule that denied — so you can see exactly what your agent sent and why it was blocked. See the [self-host audit log](/docs/concepts/audit-trail).
</Note>

## FAQ

Short, grounded answers to the questions that come up most.

<AccordionGroup>
  <Accordion title="Can Midplane see my data?" icon="eye">
    Yes — and we don't claim otherwise. Midplane logs every query as the [audit record](/docs/concepts/audit-trail), and results pass through its memory on the way back to your agent (never persisted). On self-host none of this leaves your environment; on Cloud, operators with database access can read audit data until retention expires. See [what we claim, and what we don't](/docs/security/threat-model#what-we-claim-and-what-we-dont).
  </Accordion>

  <Accordion title="Does it replace my database roles?" icon="key">
    No. Midplane is a **second layer**. It enforces policy on top of your database's own permissions — it doesn't replace role-based access control. If the connection string belongs to a privileged role, Midplane operates on top of those permissions. Give the agent a least-privilege database role as well. See the [threat model](/docs/security/threat-model).
  </Accordion>

  <Accordion title="Can an agent wipe or drop a table I marked read_write?" icon="bomb">
    No, not by default. `read_write` grants *row* writes; whole-table destruction is a separate net. The [`dangerous_statement` guardrails](/docs/concepts/table-access#beyond-table-access) — on by default since `0.9.0` — deny a `DELETE`/`UPDATE` with no `WHERE` clause and any `DROP`/`TRUNCATE`/`ALTER`, **regardless of table access**, so granting a write can't be escalated into `DELETE FROM orders` or `DROP TABLE orders`. They're configurable per flag in the [`guardrails`](/docs/reference/policy-schema) block if you really need to allow one.
  </Accordion>

  <Accordion title="Which databases does it support?" icon="database">
    Postgres, since `0.1.0` — it's the only dialect supported today. The `dialect:` key accepts only `postgres`. See [the policy engine](/docs/how-it-works).
  </Accordion>

  <Accordion title="What does it cost?" icon="tag">
    Self-host is **free** — MIT open source, no hosted dependency. Midplane Cloud has a **Free** tier plus **Pro** and **Team** plans, gating on projects, MCP tokens, and seats rather than query volume. The policy engine, audit logging, and credential isolation are on every tier. See [plans](/docs/cloud/plans).
  </Accordion>

  <Accordion title="Is there telemetry?" icon="signal">
    Self-host sends anonymous, aggregate telemetry by default — **no SQL, no table or column names, no query results, no credentials.** Disable it with `MIDPLANE_TELEMETRY=0` or `DO_NOT_TRACK=1`; inspect what's sent with `MIDPLANE_TELEMETRY=debug`. See [telemetry](/docs/self-host/operations#telemetry-and-privacy).
  </Accordion>
</AccordionGroup>

## Get help

If the fixes above don't cover it:

<Tabs>
  <Tab title="Cloud">
    Email [support@midplane.ai](mailto:support@midplane.ai) — also the **Email support** link in the dashboard sidebar. A human reads and answers.
  </Tab>

  <Tab title="Self-host & community">
    Ask in [GitHub Discussions](https://github.com/midplaneai/midplane/discussions), or file a bug with [GitHub issues](https://github.com/midplaneai/midplane/issues/new/choose). A self-host build's in-app help link points here too.
  </Tab>
</Tabs>

Found a security vulnerability? **Don't** open a public issue — see [report a vulnerability](/docs/security/threat-model#report-a-vulnerability).

## Still stuck?

<Columns cols={2}>
  <Card title="Denial reasons reference" icon="message-circle-warning" href="/docs/reference/denial-reasons" horizontal>
    Every denial message Midplane returns, and what to do about it.
  </Card>

  <Card title="How Midplane works" icon="cog" href="/docs/how-it-works" horizontal>
    The parse → policy → audit → execute pipeline behind every decision.
  </Card>
</Columns>
