google-gemini/gemini-cli · error
Cannot resume from eventId ${options.eventId} before agent_s
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.
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)
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: ${options.eventId}
- LegacyAgentSession.send() only supports message sends for th
- LegacyAgentSession.send() cannot be called while a stream is
- Invalid taskId: ${taskId}
- Security violation: Null byte detected in path.
AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12).
Data as JSON: /api/errors/3c9c84da772883bc.
Report an issue: GitHub.