thedotmack/claude-mem · error
project_id must belong to team_id
Error message
project_id must belong to team_id
What it means
Thrown by assertProjectOwnership (shared helper in utils.ts). It queries projects by (id=projectId, team_id=teamId); a missing row means the project is not owned by that team. This is the foundational tenancy check called at the top of create() paths across repositories.
Source
Thrown at src/storage/postgres/utils.ts:62
text: string,
values: unknown[] = []
): Promise<T | null> {
const result = await client.query<T>(text, values);
return result.rows[0] ?? null;
}
export async function assertProjectOwnership(
client: PostgresQueryable,
projectId: string,
teamId: string
): Promise<void> {
const row = await queryOne<{ id: string }>(
client,
'SELECT id FROM projects WHERE id = $1 AND team_id = $2',
[projectId, teamId]
);
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');
}
}View on GitHub (pinned to d768ba3643)
Solutions
- Verify (projectId, teamId) pairing against projects before calling repository methods.
- Derive teamId from the authenticated session/request context and ensure projectId belongs to it.
- Use the exact project id returned by project creation.
Example fix
// before
await observations.create({ projectId, teamId, content });
// after
await assertProjectOwnership(client, projectId, teamId); // or a guard that returns boolean
if (!await projectExists(projectId, teamId)) throw new Error('project not in team');
await observations.create({ projectId, teamId, content }); Defensive patterns
Strategy: validation
Validate before calling
async function projectExists(client, projectId, teamId){ const r = await client.query('SELECT 1 FROM projects WHERE id=$1 AND team_id=$2', [projectId, teamId]); return r.rowCount>0; } Prevention
- Resolve team from auth context once; never trust client-supplied teamId.
- Keep a projects lookup helper and call it at the API boundary.
When it happens
Trigger: Calling any repository create/validate method with a projectId that is not a row in projects for the given teamId. Fires before source-specific checks because assertProjectOwnership runs first.
Common situations: Wrong team context, project id from another team, deleted project, id typo, or thread/request-local team id not set correctly.
Related errors
- agent_event source_id must belong to project_id and team_id
- server_session_id must match the agent_event server_session_
- observation_reindex source_id must belong to project_id and
- observation_id does not exist
- generation_job_id must belong to project_id and team_id
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/6125ca6f71e5594a.
Report an issue: GitHub.