{"record":{"id":"929d1ad6838514ff","repo":"thedotmack/claude-mem","slug":"audit-log-insert-failed","errorCode":null,"errorMessage":"audit log insert failed","messagePattern":"audit log insert failed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/server/routes/v1/ServerV1PostgresRoutes.ts","lineNumber":1354,"sourceCode":"    // details win on key conflict so explicit overrides still work.\n    const detailsWithRequestId: Record<string, unknown> = {\n      ...(req.requestId ? { requestId: req.requestId } : {}),\n      ...(details ?? {}),\n    };\n    const auditInput = {\n      teamId: req.authContext?.teamId ?? null,\n      projectId: projectId ?? req.authContext?.projectId ?? null,\n      actorId,\n      apiKeyId: req.authContext?.apiKeyId ?? null,\n      action,\n      resourceType: resolveAuditResourceType(action),\n      resourceId: targetId,\n      details: detailsWithRequestId,\n    };\n    try {\n      await repo.createAuditLog(auditInput);\n    } catch (error) {\n      logger.warn('SYSTEM', 'audit log insert failed', {\n        action,\n        requestId: req.requestId ?? null,\n        error: error instanceof Error ? error.message : String(error),\n      });\n    }\n  }\n\n  // Phase 11 — paginated job listing for team/project queue endpoints.\n  // Phase 12 — extended with `sourceType`, `since`, and (optional) payload\n  // selection. Filtering is enforced in SQL (WHERE team_id [, project_id,\n  // status, source_type, created_at]). Application-layer filtering is never\n  // trusted alone for tenant scope.\n  private async listJobsForScope(input: {\n    teamId: string;\n    projectId: string | null;\n    status: string | null;\n    sourceType?: string | null;\n    limit: number;","sourceCodeStart":1336,"sourceCodeEnd":1372,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/server/routes/v1/ServerV1PostgresRoutes.ts#L1336-L1372","documentation":"After mutating admin or queue actions, the route writes an audit record via repo.createAuditLog. The insert is best-effort: on rejection it logs this warning (with the action and requestId) and the user request still succeeds, but that action has no row in the audit log, which matters for compliance and traceability.","triggerScenarios":"Any audited v1 route call while the audit_logs insert fails: migrations not applied (missing table or columns), an FK violation for actorId or apiKeyId, a database connection blip, or schema drift between createAuditLog and the deployed table.","commonSituations":"Partial migrations during deploys; Postgres restart or failover mid-request; actor rows deleted between request and audit write; column type mismatches after an upgrade.","solutions":["Pull the warn line's action, requestId, and error fields: the error names the exact database failure","Run pending migrations so audit_logs matches createAuditLog's columns","If FK targets are the cause, confirm the actor and api-key rows exist (or that nullable handling matches the schema)","For transient database blips, note the audit gap and re-issue the action if a complete trail is required"],"exampleFix":"// before\nawait repo.createAuditLog(auditInput);\n\n// after: retry transient failures, give up loudly after N attempts\nfor (let attempt = 1; attempt <= 3; attempt++) {\n  try {\n    await repo.createAuditLog(auditInput);\n    break;\n  } catch (error) {\n    const transient = isPgDatabaseError(error)\n      && ['57P01', '40001', '08000', '08006', '55P03'].includes(error.code);\n    if (!transient || attempt === 3) {\n      logger.warn('SYSTEM', 'audit log insert failed', { action, requestId, error: String(error) });\n      break;\n    }\n    await new Promise((resolve) => setTimeout(resolve, 100 * 2 ** attempt));\n  }\n}","handlingStrategy":"retry","validationCode":"// Pre-deploy check: the audit table must exist before audited routes ship\nconst { rows } = await pool.query(\"SELECT to_regclass('audit_logs') AS table_name\");\nif (!rows[0].table_name) throw new Error('audit_logs missing: run migrations first');","typeGuard":"function isPgDatabaseError(error: unknown): error is { code: string; detail?: string; message: string } {\n  return typeof error === 'object' && error !== null && 'code' in error\n    && typeof (error as { code: unknown }).code === 'string';\n}","tryCatchPattern":"try {\n  await repo.createAuditLog(auditInput);\n} catch (error) {\n  if (isPgDatabaseError(error) && ['08000', '08006', '40001', '55P03'].includes(error.code)) {\n    // transient: retry with backoff before accepting the audit gap\n  } else {\n    logger.warn('SYSTEM', 'audit log insert failed', { action, requestId, error: String(error) });\n  }\n}","preventionTips":["Apply migrations before rolling out API versions that write new audit columns","Alert on the 'audit log insert failed' log pattern so gaps are noticed, not silently accepted","Keep audit inserts out of long transactions to avoid lock-cascade failures","Track audit write failure rate as a compliance metric"],"tags":["audit-log","postgres","insert","compliance"],"backgroundTag":"database-insert-failed","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}