{"record":{"id":"95df5d6678b24df3","repo":"abhigyanpatwari/GitNexus","slug":"write-operations-are-not-allowed-the-pool-adapter","errorCode":null,"errorMessage":"Write operations are not allowed. The pool adapter is read-only.","messagePattern":"Write operations are not allowed\\. The pool adapter is read-only\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/lbug/pool-adapter.ts","lineNumber":1056,"sourceCode":"  const conn = await checkout(entry);\n  silenceStdout();\n  activeQueryCount++;\n  let queryResult: lbug.QueryResult | lbug.QueryResult[] | undefined;\n  try {\n    const stmt = await withTimeout(conn.prepare(cypher), QUERY_TIMEOUT_MS, 'Prepare');\n    if (!stmt.isSuccess()) {\n      const errMsg = await stmt.getErrorMessage();\n      throw new Error(`Prepare failed: ${errMsg}`);\n    }\n    queryResult = await withTimeout(conn.execute(stmt, params), QUERY_TIMEOUT_MS, 'Execute');\n    const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;\n    const rows = await result.getAll();\n    return rows;\n  } catch (err) {\n    if (isReadOnlyDbError(err)) {\n      // Preserve the native error as `cause` so the original frame/message is\n      // not lost behind the friendly read-only message (#2068 follow-up).\n      throw new Error('Write operations are not allowed. The pool adapter is read-only.', {\n        cause: err,\n      });\n    }\n    throw err;\n  } finally {\n    // Close the native QueryResult cursor(s) before returning the connection —\n    // getAll() drains rows but does not release the native cursor, so without\n    // this the cursor leaks for the connection's lifetime (#2068 follow-up).\n    // Best-effort via the shared helper; never masks the query result or a real\n    // error.\n    if (queryResult) await closeQueryResults(queryResult);\n    activeQueryCount--;\n    restoreStdout();\n    checkin(entry, conn);\n  }\n};\n\n/**","sourceCodeStart":1038,"sourceCodeEnd":1074,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/src/core/lbug/pool-adapter.ts#L1038-L1074","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure write operations go through the singleton adapter (lbug-adapter.ts withLbugDb / executeQuery), not the pool adapter","If you're calling executeParameterized from serve/MCP code, verify the query is read-only (MATCH/RETURN/WITH only)","If a write is genuinely needed, run `gitnexus analyze` which uses the write-capable singleton adapter","Check the `cause` property of the error for the original native LadybugDB message — it confirms the operation was a write"],"exampleFix":"// before — write query through read-only pool adapter\nawait executeParameterized(repoId, 'CREATE (n:Test {name: $name})', { name: 'x' });\n// after — use the write-capable singleton adapter path (during analyze)\nawait withLbugDb(async (c) => { await c.query('CREATE (n:Test {name: \"x\"})'); });","handlingStrategy":"validation","validationCode":"// Detect write operations in Cypher before sending to the read-only pool\nconst WRITE_CLAUSES = /^(CREATE|MERGE|DELETE|SET|REMOVE|DROP|COPY|INSERT|UPDATE)\\b/i;\nfunction isWriteQuery(cypher: string): boolean {\n  return WRITE_CLAUSES.test(cypher.trim());\n}\n// Before calling executeParameterized:\nif (isWriteQuery(cypher)) {\n  throw new Error('Write queries must go through the singleton adapter, not the read-only pool');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const rows = await executeParameterized(repoId, cypher, params);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('read-only')) {\n    // Routed a write to the pool — use the singleton adapter instead\n    logger.error('Attempted write on read-only pool — use withLbugDb for writes');\n  }\n  throw e;\n}","preventionTips":["Enforce a code-level separation: pool adapter for reads (serve/MCP), singleton adapter for writes (analyze)","Add a pre-query check that rejects write clauses in the pool adapter","Review code changes that modify Cypher in serve/MCP paths — ensure no write clauses sneak in","Use the `cause` property on the error to inspect the original native LadybugDB read-only message"],"tags":["ladybugdb","read-only","pool-adapter","write-guard","serve"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}