{"record":{"id":"3c9c84da772883bc","repo":"google-gemini/gemini-cli","slug":"cannot-resume-from-eventid-options-eventid-befo","errorCode":null,"errorMessage":"Cannot resume from eventId ${options.eventId} before agent_start for stream ${trackedStreamId}","messagePattern":"Cannot resume from eventId (.+?) before agent_start for stream (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agent/agent-session.ts","lineNumber":162,"sourceCode":"        } else if (\n          firstAgentStartIndex !== -1 &&\n          firstAgentStartIndex <= index\n        ) {\n          replayStartIndex = index + 1;\n          agentActivityStarted = true;\n        } else if (firstAgentStartIndex !== -1) {\n          // A pre-agent_start cursor can be resumed once the corresponding\n          // agent activity is already present in history. Because stream()\n          // yields only agent_start -> agent_end, replay begins at agent_start\n          // rather than at the original pre-start event.\n          replayStartIndex = firstAgentStartIndex;\n        } else {\n          // Consumers can only resume by eventId once the corresponding stream\n          // has entered the agent_start -> agent_end lifecycle in history.\n          // Without a recorded agent_start, this wrapper cannot distinguish\n          // \"agent activity may start later\" from \"this send was acknowledged\n          // without agent activity\" without risking an infinite wait.\n          throw new Error(\n            `Cannot resume from eventId ${options.eventId} before agent_start for stream ${trackedStreamId}`,\n          );\n        }\n      } else if (options.streamId) {\n        const index = currentEvents.findIndex(\n          (e) => e.type === 'agent_start' && e.streamId === options.streamId,\n        );\n        if (index !== -1) {\n          replayStartIndex = index;\n        }\n      } else {\n        const activeStarts = currentEvents.filter(\n          (e) => e.type === 'agent_start',\n        );\n        for (let i = activeStarts.length - 1; i >= 0; i--) {\n          const start = activeStarts[i];\n          if (\n            !currentEvents.some(","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/packages/core/src/agent/agent-session.ts#L144-L180","documentation":"Thrown by the same resume-by-eventId path when the referenced event exists but predates the `agent_start` event for its stream and no `agent_start` has been recorded for that stream at all. The session uses `agent_start` as the replay anchor; without it the wrapper cannot distinguish 'agent activity may begin later' from 'this send was acknowledged with no agent activity' and would risk an infinite wait, so it refuses.","triggerScenarios":"Resuming from an eventId whose streamId has no `agent_start` in the events array (the stream was acknowledged but never entered the agent lifecycle). This covers the case where `firstAgentStartIndex === -1` and the resume event is not an `agent_end`.","commonSituations":"Resuming immediately after a send that was queued but not yet picked up by an agent; resuming from an event emitted during a failed/cancelled stream that never produced `agent_start`; race where the consumer persisted an id before the lifecycle event landed.","solutions":["Wait until the stream has emitted `agent_start` before attempting to resume by eventId.","Resume by `streamId` against an already-running stream instead of by pre-start eventId.","If the stream was abandoned, start a fresh send rather than resuming.","Replay the event log and confirm an `agent_start` with the matching `streamId` is present before calling resume."],"exampleFix":"// before — resuming from a pre-start event\nawait session.send({ eventId: ackEventId }); // throws\n\n// after — wait for agent_start, then resume by streamId\nconst started = session.events.find(\n  (e) => e.type === 'agent_start' && e.streamId === ackEventIdStreamId,\n);\nif (started) {\n  await session.send({ streamId: ackEventIdStreamId });\n}","handlingStrategy":"validation","validationCode":"function assertStreamStarted(\n  events: { type: string; streamId?: string }[],\n  streamId?: string,\n) {\n  if (!streamId) return;\n  const started = events.some(\n    (e) => e.type === 'agent_start' && e.streamId === streamId,\n  );\n  if (!started) {\n    throw new Error(`Stream ${streamId} has no agent_start; wait or start fresh.`);\n  }\n}\n\nconst streamId = events.find((e) => e.id === eventId)?.streamId;\nassertStreamStarted(events, streamId);","typeGuard":"function hasAgentStarted(\n  events: { type: string; streamId?: string }[],\n  streamId: string,\n): boolean {\n  return events.some((e) => e.type === 'agent_start' && e.streamId === streamId);\n}","tryCatchPattern":"try {\n  await session.send({ eventId });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('before agent_start')) {\n    // wait for the stream to produce agent_start, then resume by streamId\n    return;\n  }\n  throw e;\n}","preventionTips":["Wait for the `agent_start` event before persisting a resumable cursor.","When in doubt, resume by streamId — the resume logic can resolve to agent_start.","Avoid persisting pre-start acknowledgements as resume points."],"tags":["agent-session","resume","lifecycle","validation"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}