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
- Capture the response's requestId and correlate it in server logs to see the true DB error
- Retry idempotent writes with exponential backoff (event batches are replayable)
- 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
- Log requestId on every 5xx so server-side root causes are findable
- Keep event batches replayable: persist failed batches to a dead-letter queue
- Watch pool saturation and DB health metrics when these 500s cluster
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
- internal_error
- Postgres requires CLAUDE_MEM_SERVER_DATABASE_URL
- Settings file is corrupted. Delete ${settingsPath} to reset.
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/2295accf207a5c20.
Report an issue: GitHub.