google-gemini/gemini-cli · error
Cannot resume from eventId
Error message
Cannot resume from eventId ${options.eventId} before agent_start for stream ${trackedStreamId} What it means
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.
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.
Example fix
// before — resuming from a pre-start event
await session.send({ eventId: ackEventId }); // throws
// after — wait for agent_start, then resume by streamId
const started = session.events.find(
(e) => e.type === 'agent_start' && e.streamId === ackEventIdStreamId,
);
if (started) {
await session.send({ streamId: ackEventIdStreamId });
} Defensive patterns
Strategy: validation
Validate before calling
function assertStreamStarted(
events: { type: string; streamId?: string }[],
streamId?: string,
) {
if (!streamId) return;
const started = events.some(
(e) => e.type === 'agent_start' && e.streamId === streamId,
);
if (!started) {
throw new Error(`Stream ${streamId} has no agent_start; wait or start fresh.`);
}
}
const streamId = events.find((e) => e.id === eventId)?.streamId;
assertStreamStarted(events, streamId); Type guard
function hasAgentStarted(
events: { type: string; streamId?: string }[],
streamId: string,
): boolean {
return events.some((e) => e.type === 'agent_start' && e.streamId === streamId);
} Try / catch
try {
await session.send({ eventId });
} catch (e) {
if (e instanceof Error && e.message.includes('before agent_start')) {
// wait for the stream to produce agent_start, then resume by streamId
return;
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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`.
Common situations: 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.
Related errors
- Unknown eventId
- LegacyAgentSession.send() cannot be called while a stream…
- LegacyAgentSession.send() only supports message sends for…
- Agent ' ' not found.
- Agent card is missing.
AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12).
Data as JSON: /api/errors/3c9c84da772883bc.
Report an issue: GitHub.
Appendix: source
Thrown at packages/core/src/agent/agent-session.ts:162
} else if (
firstAgentStartIndex !== -1 &&
firstAgentStartIndex <= index
) {
replayStartIndex = index + 1;
agentActivityStarted = true;
} else if (firstAgentStartIndex !== -1) {
// A pre-agent_start cursor can be resumed once the corresponding
// agent activity is already present in history. Because stream()
// yields only agent_start -> agent_end, replay begins at agent_start
// rather than at the original pre-start event.
replayStartIndex = firstAgentStartIndex;
} else {
// Consumers can only resume by eventId once the corresponding stream
// has entered the agent_start -> agent_end lifecycle in history.
// Without a recorded agent_start, this wrapper cannot distinguish
// "agent activity may start later" from "this send was acknowledged
// without agent activity" without risking an infinite wait.
throw new Error(
`Cannot resume from eventId ${options.eventId} before agent_start for stream ${trackedStreamId}`,
);
}
} else if (options.streamId) {
const index = currentEvents.findIndex(
(e) => e.type === 'agent_start' && e.streamId === options.streamId,
);
if (index !== -1) {
replayStartIndex = index;
}
} else {
const activeStarts = currentEvents.filter(
(e) => e.type === 'agent_start',
);
for (let i = activeStarts.length - 1; i >= 0; i--) {
const start = activeStarts[i];
if (
!currentEvents.some(View on GitHub (pinned to 5024443c72)