google-gemini/gemini-cli · error

Unknown eventId: ${options.eventId}

Error message

Unknown eventId: ${options.eventId}

What it means

Thrown by the resume-by-eventId logic in the agent session when the caller passes an `options.eventId` that does not match any event in the in-memory `this._protocol.events` log. The lookup uses `findIndex` over recorded event ids; a -1 result means the cursor the consumer is asking to resume from is unknown to this session — typically because it belongs to a different session, was pruned, or was never persisted.

Source

Thrown at packages/core/src/agent/agent-session.ts:130

      }

      queueVisibleEvent(event);

      const currentResolve = resolve;
      next = new Promise<void>((r) => {
        resolve = r;
      });
      currentResolve?.();
    });

    try {
      const currentEvents = this._protocol.events;
      let replayStartIndex = -1;

      if (options.eventId) {
        const index = currentEvents.findIndex((e) => e.id === options.eventId);
        if (index === -1) {
          throw new Error(`Unknown eventId: ${options.eventId}`);
        }

        const resumeEvent = currentEvents[index];
        trackedStreamId = resumeEvent.streamId;
        const firstAgentStartIndex = currentEvents.findIndex(
          (event) =>
            event.type === 'agent_start' && event.streamId === trackedStreamId,
        );

        if (resumeEvent.type === 'agent_end') {
          replayStartIndex = index + 1;
          agentActivityStarted = true;
          done = true;
        } else if (
          firstAgentStartIndex !== -1 &&
          firstAgentStartIndex <= index
        ) {
          replayStartIndex = index + 1;

View on GitHub (pinned to 5024443c72)

Solutions

  1. Verify the eventId was issued by *this* session instance — check it against `session.events` before calling resume.
  2. If resuming across processes, rehydrate the event log (or use `options.streamId` instead, which the resume flow can resolve to `agent_start`).
  3. Confirm the id is the raw server value and not a wrapped/quoted form.
  4. If the event was pruned, fall back to resuming by `streamId` or by starting a new stream.

Example fix

// before
await session.send({ eventId: staleIdFromAnotherProcess });

// after
const known = session.events.some((e) => e.id === candidateId);
if (!known) {
  await session.send({ streamId: lastStreamId });
} else {
  await session.send({ eventId: candidateId });
}
Defensive patterns

Strategy: validation

Validate before calling

function assertEventKnown(session: { events: { id?: string }[] }, eventId?: string) {
  if (eventId && !session.events.some((e) => e.id === eventId)) {
    throw new Error(`eventId ${eventId} not in this session's history`);
  }
}

assertEventKnown(session, options?.eventId);

Type guard

function isKnownEventId(session: { events: { id?: string }[] }, id?: string): boolean {
  return !id || session.events.some((e) => e.id === id);
}

Try / catch

try {
  await session.send({ eventId });
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Unknown eventId')) {
    // fall back to resuming by streamId, or start fresh
    await session.send({ streamId: lastStreamId });
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `send` (or equivalent resume entry) with `options.eventId` set to a value not present in the current session's event history. Common with cross-session replay, after a session restart that did not reload the event log, or when a caller serializes an event id from one process and replays it in another.

Common situations: Persisting an event id to a database and resuming in a new process that did not rehydrate `_protocol.events`; passing a client-generated id instead of the server-issued event id; trimming old events then resuming from a trimmed id; typo or copy-paste error in the id.

Related errors


AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12). Data as JSON: /api/errors/68b307f53f768d0d. Report an issue: GitHub.