abhigyanpatwari/GitNexus · error · Error

Write operations are not allowed. The pool adapter is read-o

Error message

Write operations are not allowed. The pool adapter is read-only.

What it means

Thrown by executeParameterized when the underlying LadybugDB error is classified as a read-only DB error by isReadOnlyDbError. The pool adapter opens databases in read-only mode for query serving (MCP/serve contexts); any write operation (CREATE, MERGE, DELETE, SET, REMOVE, DROP, COPY) triggers a native read-only violation. The original native error is preserved as the `cause` property so the original stack frame and message are not lost. This is a user-facing guard: the pool adapter is intentionally read-only and must never be used for writes.

Source

Thrown at gitnexus/src/core/lbug/pool-adapter.ts:1056

  const conn = await checkout(entry);
  silenceStdout();
  activeQueryCount++;
  let queryResult: lbug.QueryResult | lbug.QueryResult[] | undefined;
  try {
    const stmt = await withTimeout(conn.prepare(cypher), QUERY_TIMEOUT_MS, 'Prepare');
    if (!stmt.isSuccess()) {
      const errMsg = await stmt.getErrorMessage();
      throw new Error(`Prepare failed: ${errMsg}`);
    }
    queryResult = await withTimeout(conn.execute(stmt, params), QUERY_TIMEOUT_MS, 'Execute');
    const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
    const rows = await result.getAll();
    return rows;
  } catch (err) {
    if (isReadOnlyDbError(err)) {
      // Preserve the native error as `cause` so the original frame/message is
      // not lost behind the friendly read-only message (#2068 follow-up).
      throw new Error('Write operations are not allowed. The pool adapter is read-only.', {
        cause: err,
      });
    }
    throw err;
  } finally {
    // Close the native QueryResult cursor(s) before returning the connection —
    // getAll() drains rows but does not release the native cursor, so without
    // this the cursor leaks for the connection's lifetime (#2068 follow-up).
    // Best-effort via the shared helper; never masks the query result or a real
    // error.
    if (queryResult) await closeQueryResults(queryResult);
    activeQueryCount--;
    restoreStdout();
    checkin(entry, conn);
  }
};

/**

View on GitHub (pinned to d540b00184)

Solutions

  1. Ensure write operations go through the singleton adapter (lbug-adapter.ts withLbugDb / executeQuery), not the pool adapter
  2. If you're calling executeParameterized from serve/MCP code, verify the query is read-only (MATCH/RETURN/WITH only)
  3. If a write is genuinely needed, run `gitnexus analyze` which uses the write-capable singleton adapter
  4. Check the `cause` property of the error for the original native LadybugDB message — it confirms the operation was a write

Example fix

// before — write query through read-only pool adapter
await executeParameterized(repoId, 'CREATE (n:Test {name: $name})', { name: 'x' });
// after — use the write-capable singleton adapter path (during analyze)
await withLbugDb(async (c) => { await c.query('CREATE (n:Test {name: "x"})'); });
Defensive patterns

Strategy: validation

Validate before calling

// Detect write operations in Cypher before sending to the read-only pool
const WRITE_CLAUSES = /^(CREATE|MERGE|DELETE|SET|REMOVE|DROP|COPY|INSERT|UPDATE)\b/i;
function isWriteQuery(cypher: string): boolean {
  return WRITE_CLAUSES.test(cypher.trim());
}
// Before calling executeParameterized:
if (isWriteQuery(cypher)) {
  throw new Error('Write queries must go through the singleton adapter, not the read-only pool');
}

Try / catch

try {
  const rows = await executeParameterized(repoId, cypher, params);
} catch (e) {
  if (e instanceof Error && e.message.includes('read-only')) {
    // Routed a write to the pool — use the singleton adapter instead
    logger.error('Attempted write on read-only pool — use withLbugDb for writes');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling executeParameterized (the pool adapter's query function) with a Cypher query that performs a write operation — CREATE, MERGE with create, DELETE, SET, REMOVE, DROP, or COPY FROM — in a read-only serve/MCP context. The pool adapter is used by `gitnexus serve` and the MCP server; writes go through the singleton adapter (lbug-adapter.ts) during analyze, not the pool.

Common situations: An MCP tool or serve endpoint accidentally issues a write query through the pool adapter instead of the singleton adapter; a developer testing queries against a serve instance with a mutation; a bug where a read query was rewritten to include a MERGE/SET clause; calling the wrong adapter function for a write operation.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/95df5d6678b24df3. Report an issue: GitHub.