earendil-works/pi · error · SessionError
invalid_lane
invalid_lane
Error message
Lane not found: ${lane} What it means
Storage-level counterpart of the facade's invalid_lane throw: SessionState.requireLane maps a lane name to its current leaf and throws when the lane is unknown. Every lane-addressed write passes through it — moveLane, appendEntry (which resolves the lane's leaf as parentId), appendRecord (records carry a lane field) — plus fork target resolution. Only 'main' exists until createLane adds lanes, so the error means a write was addressed to a lane that was never created in this session.
Source
Thrown at packages/agent/src/harness/session/state.ts:79
cachedTokens: 0,
uncachedTokens: 0,
totalTokens: 0,
costTotal: 0,
};
private name: string | undefined;
private readonly labels = new Map<string, string>();
get nextSequence(): number {
return this.sequence + 1;
}
getLanes(): LanePointer[] {
return [...this.lanes].map(([lane, leafId]) => ({ lane, leafId }));
}
requireLane(lane: string): string | null {
const leafId = this.lanes.get(lane);
if (leafId === undefined) throw new SessionError("invalid_lane", `Lane not found: ${lane}`);
return leafId;
}
validateNewLane(lane: string): void {
if (this.lanes.has(lane)) throw new SessionError("already_exists", `Lane already exists: ${lane}`);
}
validateTarget(targetId: string | null): void {
if (targetId !== null && !this.entriesById.has(targetId)) {
throw new SessionError("not_found", `Entry not found: ${targetId}`);
}
}
validateUnusedId(id: string): void {
if (this.usedIds.has(id)) throw new SessionError("already_exists", `Session id already exists: ${id}`);
}
applyMutation(mutation: SessionMutation, invalid: InvalidMutation = invalidMutation): void {View on GitHub (pinned to 4af9d21d3b)
Solutions
- Create the lane before writing: await session.createLane(lane, null)
- Check existence first: (await session.getLanes()).some((p) => p.lane === lane)
- Restrict lane names to a fixed app-level set and validate incoming names against it
- After a non-tree fork, recreate side lanes before writing to them
Example fix
// before
await session.appendRecord({ id: runId, lane: 'draft', type: 'operation_started', sourceLeafId: null, intent: { kind: 'run', originalPrompt: [], initialMessages: [] } });
// after
await session.createLane('draft', null);
await session.appendRecord({ id: runId, lane: 'draft', type: 'operation_started', sourceLeafId: null, intent: { kind: 'run', originalPrompt: [], initialMessages: [] } }); Defensive patterns
Strategy: validation
Validate before calling
const laneExists = async (session: Session, lane: string): Promise<boolean> =>
(await session.getLanes()).some((p) => p.lane === lane);
if (!(await laneExists(session, record.lane))) {
await session.createLane(record.lane, null);
}
await session.appendRecord(record); Try / catch
try {
await session.moveLane('draft', target);
} catch (error) {
if (error instanceof SessionError && error.code === 'invalid_lane') {
await session.createLane('draft', null); // then retry, or route the write to 'main'
} else {
throw error;
}
} Prevention
- Create lanes before any write addresses them
- Derive lane names from a fixed app-level set; validate external input against getLanes()
- Remember only 'main' exists after create() and after non-tree forks
- Swallowing an earlier createLane error invites this throw later — propagate setup failures
When it happens
Trigger: session.moveLane('draft', to) or appendRecord({ lane: 'draft', ... }) before createLane('draft', null); appending through a view whose lane was never created; a typo or casing error in the lane name; writing to a side lane on a fork that only has 'main'.
Common situations: Lane names taken from user or LLM input without validation; assuming a non-tree fork preserved the source session's lanes; an earlier createLane failing and the error being swallowed before the write path runs.
Related errors
AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24).
Data as JSON: /api/errors/903d8dd8d62ee346.
Report an issue: GitHub.