earendil-works/pi · error · SessionError

already_exists

already_exists

Error message

Lane already exists: ${lane}

What it means

SessionState.validateNewLane rejects createLane calls whose lane name already exists in the session's lane map. That map always starts with the default lane 'main' (state.ts:57), so re-creating 'main' or any lane previously created via createLane or a lane mutation fails. Lane names are unique per session and are never reclaimed.

Source

Thrown at packages/agent/src/harness/session/state.ts:84

	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 {
		const seq =
			mutation.kind === "entry"
				? mutation.entry.seq
				: mutation.kind === "record"
					? mutation.record.seq

View on GitHub (pinned to 4af9d21d3b)

Solutions

  1. Pick a lane name not present in state.getLanes()
  2. Treat SessionError code 'already_exists' as success if you want idempotent lane creation
  3. Generate lane names from a uuid or counter instead of fixed strings
  4. Check getLanes()/requireLane() before creating so the call only happens for missing lanes

Example fix

// before
session.createLane('main', leafId); // throws already_exists

// after
if (!state.getLanes().some((p) => p.lane === 'main')) {
  session.createLane('main', leafId);
}
Defensive patterns

Strategy: validation

Validate before calling

// Skip creation when the lane already exists
const laneExists = (state: SessionState, lane: string): boolean =>
  state.getLanes().some((pointer) => pointer.lane === lane);

if (!laneExists(state, lane)) session.createLane(lane, leafId);

Type guard

import { SessionError } from './types.ts';

function isSessionError(e: unknown, code?: string): e is SessionError {
  return e instanceof SessionError && (code === undefined || e.code === code);
}

Try / catch

try {
  session.createLane(lane, leafId);
} catch (e) {
  if (isSessionError(e, 'already_exists')) return; // idempotent create
  throw e;
}

Prevention

When it happens

Trigger: createLane('main') (exists by default in every SessionState); createLane with a name already returned by getLanes(); a retry wrapper re-issuing createLane after a timeout when the first attempt actually committed; two concurrent UI actions racing to create the same lane name.

Common situations: Hardcoding 'main' as a new lane name; idempotency-style retry logic that assumes the first create failed; rehydrating a session from disk and re-running the setup code that already created its lanes.

Related errors


AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24). Data as JSON: /api/errors/bfad6bca8ebf9896. Report an issue: GitHub.