thedotmack/claude-mem · warning

NotFound

NotFound

Error message

Event not found

What it means

404 from GET /v1/events/:id/observations when no agent_events row matches the id for the caller's team. The lookup is team-scoped (WHERE id = $1 AND team_id = $2), so an event that exists under another team is indistinguishable from one that never existed — deliberate, to avoid revealing existence. A nonexistent id and a cross-tenant id return the same response.

Source

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

      res.json({ event: serializeEvent(fullEvent) });
    }));

    // GET /v1/events/:id/observations — list observations linked to event via observation_sources.
    // Scope is enforced by joining observations.team_id = $teamId and the
    // event ownership check before any rows are returned. Cross-tenant
    // requests are reported as 404 to avoid revealing existence.
    app.get('/v1/events/:id/observations', readAuth, this.asyncHandler(async (req, res) => {
      const teamId = this.requireTeamId(req, res);
      if (!teamId) return;
      const id = this.routeParam(req.params.id);

      const eventResult = await this.options.pool.query(
        `SELECT id, project_id FROM agent_events WHERE id = $1 AND team_id = $2`,
        [id, teamId],
      );
      const eventRow = eventResult.rows[0] as undefined | { id: string; project_id: string };
      if (!eventRow) {
        res.status(404).json({ error: 'NotFound', message: 'Event not found' });
        return;
      }
      if (!this.ensureProjectAllowed(req, res, eventRow.project_id)) return;

      const obsResult = await this.options.pool.query(
        `
          SELECT o.id, o.project_id, o.team_id, o.server_session_id, o.kind, o.content,
                 o.metadata, o.generation_key, o.created_by_job_id, o.created_at, o.updated_at,
                 os.id AS source_id_pk, os.source_type, os.source_id, os.generation_job_id, os.created_at AS source_created_at
          FROM observation_sources os
          INNER JOIN observations o ON o.id = os.observation_id
          WHERE os.source_type = 'agent_event'
            AND os.source_id = $1
            AND o.team_id = $2
            AND o.project_id = $3
          ORDER BY o.created_at ASC
        `,
        [eventRow.id, teamId, eventRow.project_id],

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Confirm the event create call returned 201 and use the returned event id verbatim.
  2. Verify the id belongs to the caller's team (events listing scoped to the key's team).
  3. Treat 404 as terminal for that id — retrying without fixing the id or scope cannot succeed.
  4. Wait for ingestion before first poll if the event was just created.
Defensive patterns

Strategy: validation

Validate before calling

// Use the id from the create response, never a hand-copied one
const created = await postJson('/v1/events', eventPayload);
const eventId: string = created.event.id;
const observations = await getJson(`/v1/events/${encodeURIComponent(eventId)}/observations`);

Type guard

interface NotFoundBody { error: string; message: string }
function isEventNotFound(res: Response, body: unknown): boolean {
  return res.status === 404 && typeof body === 'object' && body !== null &&
    (body as NotFoundBody).error === 'NotFound' && (body as NotFoundBody).message === 'Event not found';
}

Prevention

When it happens

Trigger: GET /v1/events/<wrong-or-typo'd-id>/observations; event id from a different team/environment; event not yet ingested when observations are polled right after creation; key's team differs from the event's team.

Common situations: Polling for observations before the create call completed; copying ids between staging and prod; truncating UUIDs during manual testing.

Related errors


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