thedotmack/claude-mem · error · Error
agent_event_id must belong to project_id and team_id
Error message
agent_event_id must belong to project_id and team_id
What it means
For 'agent_event' sources, assertAgentEventOwnership checks that the agent event id exists in agent_events scoped to project_id and team_id. If not found, the event is unknown, belongs to another tenant, or was deleted, so the source cannot be attached.
Solutions
- Verify the event exists: SELECT id FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3.
- Ingest/persist the agent event first, then attach the observation source using the returned id.
- Check that projectId/teamId match the tenant under which the event was stored.
Example fix
// before
await repo.addSource({ sourceType: 'agent_event', sourceId: uningestedEventId, ... });
// after
const evt = await agentEvents.ingest(event); // returns id in same project/team
await repo.addSource({ sourceType: 'agent_event', sourceId: evt.id, ... }); Defensive patterns
Strategy: validation
Validate before calling
const evt = await db.oneOrNone(
'SELECT 1 FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3',
[eventId, projectId, teamId]
);
if (!evt) throw new Error(`agent event ${eventId} not ingested in scope`); Try / catch
try {
await repo.addSource(input);
} catch (e) {
if (e instanceof Error && e.message === 'agent_event_id must belong to project_id and team_id') {
logger.warn('skip source: agent event not found', { eventId: input.sourceId });
return null;
}
throw e;
} Prevention
- Always obtain agent event ids from the ingestion API response, never construct them.
- Ingest the event and attach its observation source within one transaction.
- Verify tenant ids of events and observations come from the same context object.
When it happens
Trigger: addSource with sourceType='agent_event' where input.sourceId (the agent event id) is not a row in agent_events for the supplied projectId/teamId.
Common situations: Ingesting events from another environment; tenant id mixups; the event was purged before observations were generated; agent event ids fabricated in a client instead of returned by the ingestion API.
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
- agent_event source_id must equal agent_event_id
- generation_job_id agent_event_id must match observation…
- generation_job_id must belong to project_id and team_id
- observation_id does not exist
- observation_reindex source_id must belong to project_id and…
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/66cb2af93a9fbb5f.
Report an issue: GitHub.
Appendix: source
Thrown at src/storage/postgres/observations.ts:372
}
if (input.sourceType === 'agent_event' && row.agent_event_id !== input.agentEventId) {
throw new Error('generation_job_id agent_event_id must match observation source');
}
}
async function assertAgentEventOwnership(
client: PostgresQueryable,
agentEventId: string,
projectId: string,
teamId: string
): Promise<void> {
const row = await queryOne<{ id: string }>(
client,
'SELECT id FROM agent_events WHERE id = $1 AND project_id = $2 AND team_id = $3',
[agentEventId, projectId, teamId]
);
if (!row) {
throw new Error('agent_event_id must belong to project_id and team_id');
}
}
async function assertObservationOwnership(
client: PostgresQueryable,
observationId: string,
projectId: string,
teamId: string
): Promise<void> {
const row = await queryOne<{ id: string }>(
client,
'SELECT id FROM observations WHERE id = $1 AND project_id = $2 AND team_id = $3',
[observationId, projectId, teamId]
);
if (!row) {
throw new Error('observation_reindex source_id must belong to project_id and team_id');
}
}View on GitHub (pinned to d8bc9755e7)