# Troubleshooting

Use this page to map symptoms to status checks and recovery actions.

## Function Does Not Exist for a Registration Call

Symptom:

```text
ERROR: function graph.add_table(unknown, unknown) does not exist
HINT: No function matches the given name and argument types.
```

PostgreSQL treats a quoted table name as an `unknown` string unless you cast it
to the `regclass` table-identifier type required by pgGraph registration APIs.
This does not mean the extension or function is missing.

Incorrect:

```sql
SELECT graph.add_table('public.users', 'id');
```

Fix:

```sql
SELECT graph.add_table('public.users'::regclass, 'id');
```

Use `::regclass` for table arguments to `graph.add_table()`,
`graph.add_edge()`, traversal, path, search, and related APIs. PostgreSQL also
validates that the named relation exists when it resolves the cast.

## Stale Registration Produces PG000

Symptom:

```text
SQLSTATE XX000
DETAIL: pgGraph diagnostic: PG000
registered source relation OID ... no longer exists; re-register it
```

This error can occur after a registered table is dropped and recreated, or
after a logical restore assigns new PostgreSQL relation OIDs. The selected
graph still refers to the old relation identity.

Clear the selected graph's derived state and stale registrations, then reapply
the reviewed registration SQL:

```sql
SELECT graph.reset(true);

SELECT graph.add_table(...);
SELECT graph.add_edge(...);
-- Reapply graph.add_filter_column(...) calls when used.

SELECT * FROM graph.build();
```

`graph.reset(true)` does not modify PostgreSQL source tables or other named
graphs. It does clear every registration for the selected graph, so reapply all
required table, edge, and filter registrations before `graph.build()`.

Use this recovery only when the `PG000` message identifies a stale registered
relation or relation OID. For another `PG000` internal error, keep the full
message and reproduction details and report the issue.

## Graph Not Built

Symptom:

```text
SQLSTATE 55000
DETAIL: pgGraph diagnostic: PG003
Graph not built. Call graph.build() first.
```

Check:

```sql
SELECT node_count, edge_count, schema_status, invalid_reason
FROM graph.status();
```

Fix:

```sql
SELECT * FROM graph.build();
```

## Node Not Found

Symptom:

```text
SQLSTATE P0002
DETAIL: pgGraph diagnostic: PG010
Node not found
```

Check:

```sql
SELECT *
FROM graph.search('id', 'expected_id', table_filter := 'public.my_table'::regclass, mode := 'exact');

SELECT * FROM graph.registered_tables();
```

Likely causes:

| Cause | Fix |
|---|---|
| Wrong table or ID string | Use exact source primary-key text |
| Composite ID mismatch | Use JSON array text encoding from `graph.node_ref_string()` patterns |
| Source row added after build | `graph.apply_sync()` or `graph.build()` |
| Source row deleted/tombstoned | Rebuild or query another active node |

## Search Returns No Rows

Check that the column is registered in `graph.add_table(..., columns := ...)`:

```sql
SELECT *
FROM graph.registered_tables()
WHERE table_name = 'public.users';
```

Then test exact mode and table filter:

```sql
SELECT *
FROM graph.search(
  'name',
  'Alice',
  table_filter := 'public.users'::regclass,
  mode := 'exact',
  hydrate := true
);
```

Search uses source-table SQL, so source table indexes matter. Add normal
PostgreSQL indexes for high-volume searched columns.

## Traversal Returns Too Few Rows

Check:

```sql
SELECT *
FROM graph.status();

SELECT *
FROM graph.traverse(
  'public.users'::regclass,
  'u1',
  max_depth := 1,
  edge_types := NULL,
  direction := 'any',
  hydrate := false,
  max_nodes := 100000,
  max_frontier := 100000
);
```

Likely causes:

| Cause | Fix |
|---|---|
| `max_depth` too low | Increase `max_depth` |
| `edge_types` filter excludes edges | Remove filter or inspect `edge_types` in `graph.status()` |
| `direction` wrong for unidirectional edges | Use `out`, `in`, or register bidirectional edges |
| `max_nodes` or `max_frontier` trips | Raise circuit breakers for trusted query |
| Tenant scope excludes nodes | Pass correct `tenant` or session tenant |
| Filter column not registered/rebuilt | Register filter column and rebuild |

## Unsupported Operation With Pending Overlays

Symptom:

```text
SQLSTATE 0A000
DETAIL: pgGraph diagnostic: PG018
Unsupported graph operation
```

Likely cause:

| Cause | Fix |
|---|---|
| `graph.weighted_shortest_path()` ran while `edge_buffer_used > 0` | Run `graph.vacuum()` or `graph.maintenance()` to merge pending edge overlays, then retry |

Unweighted traversal, unweighted shortest path, and connected components include
pending edge overlays after `graph.apply_sync()`. Weighted shortest path fails
closed while overlays are pending because pending edge mutations do not carry
edge weights.

## RLS Relationship Identity Requires A Rebuild

Symptom:

```text
SQLSTATE 55000
DETAIL: pgGraph diagnostic: PG023
```

pgGraph 1.1 evaluates relationship-source RLS before an edge can affect
topology. That requires every projected edge to retain its source-row identity.
If a compatible legacy artifact does not contain a usable identity for an
RLS-active relationship mapping, pgGraph fails closed instead of treating the
edge as visible.

Rebuild from the authoritative PostgreSQL tables:

```sql
SELECT * FROM graph.build();
```

The replacement is published only after validation. If the rebuild fails or is
cancelled, the previous published generation remains authoritative, but RLS
queries that require the missing identity continue to fail closed until a
successful rebuild.

## Recursive RLS Visibility Resolution

Symptom:

```text
SQLSTATE 55000
DETAIL: pgGraph diagnostic: PG024
```

An RLS policy on a registered source table called a pgGraph topology function
while pgGraph was already asking PostgreSQL to evaluate that policy. pgGraph
rejects this recursion instead of re-entering traversal with partially built
visibility state. Move the nested graph query out of the policy, or compute the
policy predicate from ordinary PostgreSQL relations and session settings.

## Direct RLS Probe Does Not Support This Primary-Key Type

Symptom:

```text
SQLSTATE 0A000
DETAIL: pgGraph diagnostic: PG018
```

RLS identity matching supports built-in boolean, integer, text, character,
numeric, OID, and UUID primary-key columns. Date/time, array, domain,
extension, and custom types can format the same value differently under
session settings or custom I/O code. A 1.1 projection stores only the formatted
identity, so pgGraph cannot safely match those types. A topology shape fails
closed when it can consume the affected RLS-active mapping; direct node probes
validate only their requested node mapping. Use a stable supported key column
for the graph identity. `graph.rls_mode = 'legacy_bypass'` is only an authorized
maintenance escape hatch and must not be used to serve tenant-isolated reads.

## Schema Invalid Or Needs Rebuild

Symptoms:

```sql
SELECT schema_status, needs_rebuild, invalid_reason
FROM graph.status();
```

Fix after catalog or source schema changes:

```sql
SELECT * FROM graph.registered_tables();
SELECT * FROM graph.registered_edges();
SELECT * FROM graph.build();
```

If a registered column no longer exists, remove or update the registration.

## Build Fails With Memory Error

Symptom: SQLSTATE `53200`, diagnostic `PG001`.

Check:

```sql
SELECT * FROM graph.estimate();
SHOW graph.memory_limit_mb;
```

Fix:

```sql
ALTER SYSTEM SET graph.memory_limit_mb = 8192;
SELECT pg_reload_conf();
```

Or reduce registered graph scope.

## Query Or Maintenance Hits A Resource Limit

Symptom: SQLSTATE `54000`, diagnostic `PG007`. The error names the phase,
resource kind, current use, requested amount, and configured limit.

Inspect the most recent completed governed operation in the same backend:

```sql
SELECT * FROM graph.resource_status();
SHOW graph.query_memory_mb;
SHOW graph.maintenance_memory_mb;
SHOW graph.spill_disk_limit_mb;
SHOW graph.query_work_limit;
SHOW graph.operation_timeout_ms;
```

Reduce result cardinality, depth, wildcard range, hydration width, sync batch,
or compaction range before raising a limit. If you raise a memory setting,
remember that `graph.memory_limit_mb` is still the hard backend-private ceiling
after the loaded graph's residency. PostgreSQL cancellation and
`statement_timeout` are checked throughout long graph operations.

## Build Or Vacuum Locked

Symptom: SQLSTATE `55P03`, diagnostic `PG006`.

Check:

```sql
SELECT pid, state, query
FROM pg_stat_activity
WHERE query ILIKE '%graph.build%' OR query ILIKE '%graph.vacuum%' OR query ILIKE '%graph.maintenance%';
```

Wait for the current operation or investigate the owning session.

## Corrupt Or Incompatible Artifact

Symptoms: SQLSTATE `XX001`/diagnostic `PG009`, or SQLSTATE
`0A000`/diagnostic `PG011`.

Fix:

```sql
SELECT * FROM graph.build();
```

If needed:

```sql
SELECT graph.reset();
SELECT * FROM graph.build();
```

## Edge Buffer Full

Symptom: diagnostic `PG008` or `PG012`, `read_only = true`, or high
`edge_buffer_used`. The corresponding SQLSTATE is `54000` or `55000`.

Check:

```sql
SELECT edge_buffer_used, needs_vacuum, read_only, read_only_reason
FROM graph.status();
```

Fix:

```sql
SELECT * FROM graph.maintenance();
```

or:

```sql
SELECT * FROM graph.vacuum();
```

## ACL Denied

Symptom: SQLSTATE `42501`, diagnostic `PG002`.

Fix for read queries:

```sql
GRANT SELECT ON public.source_table TO app_role;
```

Fix for admin operations:

```sql
GRANT USAGE, CREATE ON SCHEMA graph TO graph_admin;
```

## Operational Failure Matrix

Use this matrix when diagnosing complex production failures relating to jobs, quotas, sync, or storage.

| Failure | Stable state | SQLSTATE / diagnostic | Visible location | Recovery action | Read impact |
|---|---|---|---|---|---|
| **Sync falls behind** | `sync_status = 'syncing'`, `sync_lag > 0` | `0A000` / `PG018` if the lag surfaces as an unsupported-operation guard | `graph.status().sync_lag`, `graph.status().pending_sync_rows`, `graph.jobs()` | Run `graph.run_due_jobs()` or `graph.run_job(job_id)` after confirming the source tables are healthy. | Serves the last valid generation. |
| **Artifacts are missing** | `graph.projection_status().artifact_validation_state = 'full_rebuild'` | `XX001` / `PG009` | `graph.status().schema_status`, `graph.projection_status().artifact_validation_state` | Run `graph.projection_repair()` or `graph.build()` to publish a fresh artifact. | Reads fail on affected paths until rebuilt. |
| **Artifact validation fails** | `graph.projection_status().artifact_validation_state = 'targeted_chunk_repair'` | `XX001` / `PG009` | `graph.projection_status().artifact_validation_state` | Run `graph.projection_repair()`; if repair cannot salvage the files, rebuild. | Reads fail on affected paths until repaired. |
| **Disk is full** | `read_only_reason` indicates storage pressure | `53100` | `graph.status().read_only_reason`, `graph.job_runs()` | Free space, then rerun `graph.run_due_jobs()` or `graph.projection_repair()`. | Serves the last valid generation. |
| **Compaction fails** | `graph.projection_status().artifact_validation_state = 'targeted_chunk_repair'` after a failed compaction run | `0A000` / `PG018` | `graph.projection_status().last_compaction_unix_micros`, `graph.job_runs()` | Fix the underlying artifact problem and rerun `graph.projection_repair()` or `graph.maintenance()`. | Serves the last valid generation. |
| **Projection ingest fails** | `pending_durable_rows > 0` and `repair_recommended = true` | `0A000` / `PG018` | `graph.projection_status().pending_durable_rows`, `graph.projection_status().repair_recommended` | Repair the projection artifact, then rerun the ingest path. | Serves the last valid generation. |
| **A job repeatedly errors** | `graph.jobs().last_status = 'retryable_failed'` or `graph.jobs().last_status = 'permanent_failed'` | `XX000` or a more specific job SQLSTATE | `graph.jobs()`, `graph.job_runs()` | Inspect the most recent run error, correct the source issue, and rerun the job. | Depends on job type. |
| **A graph exceeds quota** | `graph.graph_quota_usage().exceeded = true` | `54000` / `PG019` | `graph.graph_quota_usage()` | Raise the quota or shrink graph scope/storage. | New writes, builds, or loads can be blocked. |
| **Background workers unavailable** | `graph.jobs().last_status = 'queued'` | N/A | `graph.jobs()`, `graph.job_runs()` | Restore worker capacity, then run `graph.run_due_jobs()` manually. | Serves the last valid generation. |
| **Schema/catalog drift** | `schema_status = 'invalid'` and `needs_rebuild = true` | `0A000` / `PG018` | `graph.status().schema_status`, `graph.status().needs_rebuild` | Run `graph.build()` for the selected graph. | Reads fail on affected paths until rebuilt. |

## Maintainer Validation

If a reproducible issue requires release-class ACL, concurrency, install, or
crash validation, include the public diagnostics above in the issue report.
Maintainers can then select the appropriate focused gate from the
[Testing And Release](../contributor_guide/testing-release) guide.
