thedotmack/claude-mem · error

InternalError

InternalError

Error message

Failed to persist event

What it means

The fallback branch of handleDbError: any DB error whose message does not match the three team-belongs constraint patterns is logged server-side as '<action> failed' and answered 500 {error:'InternalError', message:'Failed to persist event'}. The generic body hides internals from the client; the real cause exists only in the server log, correlated by requestId.

Source

Thrown at src/server/routes/v1/ServerV1PostgresRoutes.ts:1321

    const byTeam = await this.options.pool.query(
      `DELETE FROM observations WHERE id = $1 AND team_id = $2`,
      [id, teamId],
    );
    return (byTeam.rowCount ?? 0) > 0;
  }

  private handleDbError(error: unknown, res: Response, action: string): void {
    const message = error instanceof Error ? error.message : String(error);
    if (
      message.includes('project_id must belong to team_id')
      || message.includes('server_session_id must belong')
      || message.includes('agent_event source_id must belong')
    ) {
      res.status(403).json({ error: 'Forbidden', message });
      return;
    }
    logger.error('SYSTEM', `${action} failed`, { error: message });
    res.status(500).json({ error: 'InternalError', message: 'Failed to persist event' });
  }

  private async auditWrite(
    req: Request,
    action: string,
    targetId: string | null,
    projectId: string | null,
    details?: Record<string, unknown>,
  ): Promise<void> {
    const repo = new PostgresAuthRepository(this.options.pool);
    const actorId = await this.resolveActorId(req);
    // Phase 12 — every audit row carries request_id when one was minted
    // so dashboards and incident triage can pivot from a single HTTP
    // request to every ingest/job/audit row it produced. Caller-supplied
    // details win on key conflict so explicit overrides still work.
    const detailsWithRequestId: Record<string, unknown> = {
      ...(req.requestId ? { requestId: req.requestId } : {}),
      ...(details ?? {}),

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Capture the response's requestId and correlate it in server logs to see the true DB error
  2. Retry idempotent writes with exponential backoff (event batches are replayable)
  3. If it persists, check DB connectivity, pool size, and recent schema migrations
Defensive patterns

Strategy: retry

Try / catch

On 500 'Failed to persist event': capture the requestId from the response/logs, retry with exponential backoff (respect idempotency of the batch), and escalate only after N attempts — the server deliberately hides the underlying DB error from the client.

Prevention

When it happens

Trigger: POST /v1/events, /v1/events/batch, /v1/sessions/start, session end, or project purge hitting a transient Postgres outage, connection-pool exhaustion, a unique-constraint violation, or a serialization failure.

Common situations: Database failover mid-deploy; pool maxed out under burst event traffic; a migration adding a NOT NULL column while old clients still submit the legacy shape.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/2295accf207a5c20. Report an issue: GitHub.