Skip to main content
When Midplane denies a query, the MCP tool result and the DECIDED audit row both carry a policy_rule naming the rule that denied, plus a human-readable reason. This page maps each rule to the cause and the fix. The reason is written for the agent. Since 0.18.0 it states the decision and any fix the agent can make to its own statement (add a WHERE clause), and never names a policy key to flip — loosening policy is operator work, which is what the Fix columns below are for. Branch on policy_rule, not on the prose; the rule identifiers are stable, the wording isn’t. The engine evaluates four rules in order, and the first rule that denies wins. The order is parse_errormulti_statementtable_accessdangerous_statement. Two are always-on guards (parse_error, multi_statement); the rest are driven by your policytable_access and the dangerous_statement guardrails default on. See the policy engine. Two more policy_rule values come from the write-approvals stage, which runs after all four rules allow.

table_access

A query referenced a table at a permission level its policy doesn’t grant. A query is denied if any referenced table fails — including writes hidden in CTEs, subqueries, UNION arms, or JOINs, caught by the recursive AST walk. There are three sub-cases:
To grant the write, mark the table read_write:
Run midplane policy test to confirm the change before you ship it.

multi_statement

The query contained more than one top-level statement. Midplane allows exactly one statement per query — stacked statements (the classic SQL-injection vector) never reach the database.
Fix: split the query into one statement per call. A trailing semicolon is fine; a second statement is not.

dangerous_statement

A guardrail denied a write categorically — regardless of table_access, so this fires even on a table you’ve marked read_write. One guard per write class, each independently toggled:
The agent-facing message names the operation and which guardrail fired, for example:
Because the guardrail runs last, a statement another rule would also deny (for example a DROP under the no-YAML deny-all-writes default) surfaces that other rule’s reason instead — the statement is blocked either way. See the guardrails schema.

parse_error

The input failed to parse. libpg_query, the real Postgres parser, decides this — anything that isn’t valid Postgres SQL is denied. Midplane never enforces policy on text it can’t read. Fix: rewrite the query as valid Postgres SQL. Midplane parses with the Postgres parser, so dialect-specific or exotic syntax it doesn’t recognize is denied — rewrite it into a supported form.

Write approvals

Write approvals run after the four rules allow (0.16.0+), so these only ever appear on a statement your policy already permits. Two of the four outcomes are real decisions and are audited as DECIDED(DENY); the other two are non-decisions — nothing ran and nobody refused, so no DECIDED row is written and no deny webhook fires. Both non-decisions return executed: false first: an agent that reads a hold as success would report a write that never happened.

Result masking

column_masking is not one of the ordered policy rules above — those decide whether a query runs. It fires when the engine can’t safely apply column masking to a query that touches a masked table, failing closed: the query is denied before it runs and all rows withheld rather than risk returning an unmasked value. Plain aggregates and joins over a masked table are not denied — they rewrite and run. The agent-facing message hints at the fix: query the masked column directly rather than through a view, a whole-row serializer, or an opaque function, so the engine can mask it at the source. See column masking for the mechanic and transforms.

Still stuck?

If a query is denied and you can’t tell why, dry-run it with midplane policy test — it prints the deciding rule and the exact agent-facing message against your policy file, with no database connection. For more, see troubleshooting.

Troubleshooting

Common denials, connection problems, and agent-wiring issues.