thedotmack/claude-mem · warning

failed to write operator audit row

Error message

failed to write operator audit row

What it means

writeOperatorAudit inserts an audit_log row (via Postgres) recording CLI operator actions (retry/cancel) on observation-generation jobs. If the INSERT fails, it logs this warning and swallows the error — the operator action itself is not rolled back, only the audit trail entry is lost.

Solutions

  1. Check the logged `error` detail in the warn payload to identify the root SQL cause.
  2. Apply pending migrations so the audit_log table and its columns exist.
  3. Ensure the DB role has INSERT permission on audit_log and pgcrypto/gen_random_uuid() is available.
  4. Verify Postgres connectivity, then re-run the operator action if the audit row matters.

Example fix

// before: gen_random_uuid() undefined
CREATE EXTENSION IF NOT EXISTS pgcrypto;
// or on PG13+
-- nothing needed; verify table exists
\d audit_log
// after: retry the action
claude-mem server jobs retry <jobId>
Defensive patterns

Strategy: validation

Validate before calling

-- Before running operator actions, verify prerequisites
SELECT to_regclass('audit_log');            -- table exists?
SELECT gen_random_uuid();                   -- pgcrypto/PG13+ available?
SELECT has_table_privilege(current_user, 'audit_log', 'INSERT');

Type guard

function isAuditInsertError(e: unknown): boolean {
  const msg = e instanceof Error ? e.message : String(e);
  return /audit_log|permission denied|gen_random_uuid|relation .* does not exist/i.test(msg);
}

Try / catch

try {
  await pool.query(insertAuditSql, params);
} catch (error) {
  logger.warn('SYSTEM', 'failed to write operator audit row', {
    action,
    jobId: job.id,
    error: error instanceof Error ? error.message : String(error),
  }); // deliberate swallow: audit loss must not fail the operator action
}

Prevention

When it happens

Trigger: Running `jobs retry` or `jobs cancel` when the audit INSERT into audit_log fails — Postgres down, table missing, gen_random_uuid() unavailable (old PG without pgcrypto), FK violation on team_id/project_id/actor columns, or permission denied on audit_log.

Common situations: Database migration not applied (audit_log table absent); PostgreSQL < 13 without pgcrypto enabled; restricted DB role lacking INSERT on audit_log; transient connection loss.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/78897017564cbf38. Report an issue: GitHub.

Appendix: source

Thrown at src/npx-cli/commands/server-jobs.ts:430

interface PoolLike {
  query: (sql: string, params: unknown[]) => Promise<{ rows: unknown[] }>;
}

async function writeOperatorAudit(
  pool: { query: (sql: string, params: unknown[]) => Promise<unknown> },
  job: JobLookup,
  action: string,
  details: Record<string, unknown>,
): Promise<void> {
  try {
    await pool.query(
      `INSERT INTO audit_log (id, team_id, project_id, actor_id, api_key_id, action, resource_type, resource_id, details)
       VALUES (gen_random_uuid(), $1, $2, NULL, NULL, $3, 'observation_generation_job', $4, $5::jsonb)`,
      [job.team_id, job.project_id, action, job.id, JSON.stringify({ ...details, source: 'cli_operator' })],
    );
  } catch (error) {
    logger.warn('SYSTEM', 'failed to write operator audit row', {
      action,
      jobId: job.id,
      error: error instanceof Error ? error.message : String(error),
    });
  }
}

function extractRetriedCount(payload: Record<string, unknown> | null | undefined): number {
  if (!payload || typeof payload !== 'object') return 0;
  const value = (payload as { retried_count?: unknown }).retried_count;
  return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.floor(value) : 0;
}

function detectDivergence(
  pg: Record<string, number>,
  bullmq: Record<string, { waiting: number; active: number; completed: number; failed: number; delayed: number; stalled: number }> | null,
): Record<string, unknown> {
  if (!bullmq) return { reason: 'bullmq_unavailable' };

View on GitHub (pinned to d8bc9755e7)