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

  1. Create the lane before writing: await session.createLane(lane, null)
  2. Check existence first: (await session.getLanes()).some((p) => p.lane === lane)
  3. Restrict lane names to a fixed app-level set and validate incoming names against it
  4. 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

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.