thedotmack/claude-mem · error · Error

server_session_id must belong to project_id and team_id

Error message

server_session_id must belong to project_id and team_id

What it means

assertSessionOwnership verifies that a server_session_id belongs to both the supplied project and team before writes proceed. The SELECT matches on id, project_id, and team_id simultaneously; any mismatch means the session is not scoped to that project/team and the error is thrown. It enforces the full three-level ownership chain (team -> project -> session).

Solutions

  1. Confirm the server_session_id was created under the same projectId and teamId in the request
  2. Re-query server_sessions for the project and use a live session ID
  3. Check for cross-environment (dev/staging/prod) configuration mixing
  4. Recreate the session under the correct project if it was deleted

Example fix

// before
await addSource({ teamId, projectId: 'projA', serverSessionId: sessionOfProjB });
// after
const sessions = await listSessions(teamId, 'projA');
await addSource({ teamId, projectId: 'projA', serverSessionId: sessions[0].id });
Defensive patterns

Strategy: validation

Validate before calling

const row = await queryOne('SELECT id FROM server_sessions WHERE id = $1 AND project_id = $2 AND team_id = $3', [serverSessionId, projectId, teamId]);
if (!row) throw new Error('session not in project/team');

Try / catch

try { await addSource(args); }
catch (e) { if (e.message.includes('server_session_id must belong')) { args.serverSessionId = await getDefaultSession(args.teamId, args.projectId); await addSource(args); } else throw e; }

Prevention

When it happens

Trigger: Calling create, validateSource, or addSource with a serverSessionId that belongs to a different project, a different team, or that no longer exists.

Common situations: Reusing a session ID across projects; passing a session ID from a dev database to a prod team; sessions deleted between listing and use; mixing IDs from two tenants in one request.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src/storage/postgres/utils.ts:78

  );
  if (!row) {
    throw new Error('project_id must belong to team_id');
  }
}

export async function assertSessionOwnership(
  client: PostgresQueryable,
  serverSessionId: string,
  projectId: string,
  teamId: string
): Promise<void> {
  const row = await queryOne<{ id: string }>(
    client,
    'SELECT id FROM server_sessions WHERE id = $1 AND project_id = $2 AND team_id = $3',
    [serverSessionId, projectId, teamId]
  );
  if (!row) {
    throw new Error('server_session_id must belong to project_id and team_id');
  }
}

export function canonicalJson(value: unknown): string {
  return JSON.stringify(sortJson(value));
}

export function deterministicKey(parts: readonly unknown[]): string {
  const fingerprint = createHash('sha256')
    .update(canonicalJson(parts))
    .digest('hex');
  return fingerprint;
}

function sortJson(value: unknown): unknown {
  if (Array.isArray(value)) {
    return value.map(sortJson);
  }

View on GitHub (pinned to d8bc9755e7)