{"record":{"id":"903d8dd8d62ee346","repo":"earendil-works/pi","slug":"invalid-lane-903d8d","errorCode":"invalid_lane","errorMessage":"Lane not found: ${lane}","messagePattern":"Lane not found: (.+?)","errorType":"exception","errorClass":"SessionError","httpStatus":null,"severity":"error","filePath":"packages/agent/src/harness/session/state.ts","lineNumber":79,"sourceCode":"\t\tcachedTokens: 0,\n\t\tuncachedTokens: 0,\n\t\ttotalTokens: 0,\n\t\tcostTotal: 0,\n\t};\n\tprivate name: string | undefined;\n\tprivate readonly labels = new Map<string, string>();\n\n\tget nextSequence(): number {\n\t\treturn this.sequence + 1;\n\t}\n\n\tgetLanes(): LanePointer[] {\n\t\treturn [...this.lanes].map(([lane, leafId]) => ({ lane, leafId }));\n\t}\n\n\trequireLane(lane: string): string | null {\n\t\tconst leafId = this.lanes.get(lane);\n\t\tif (leafId === undefined) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n\t\treturn leafId;\n\t}\n\n\tvalidateNewLane(lane: string): void {\n\t\tif (this.lanes.has(lane)) throw new SessionError(\"already_exists\", `Lane already exists: ${lane}`);\n\t}\n\n\tvalidateTarget(targetId: string | null): void {\n\t\tif (targetId !== null && !this.entriesById.has(targetId)) {\n\t\t\tthrow new SessionError(\"not_found\", `Entry not found: ${targetId}`);\n\t\t}\n\t}\n\n\tvalidateUnusedId(id: string): void {\n\t\tif (this.usedIds.has(id)) throw new SessionError(\"already_exists\", `Session id already exists: ${id}`);\n\t}\n\n\tapplyMutation(mutation: SessionMutation, invalid: InvalidMutation = invalidMutation): void {","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/earendil-works/pi/blob/4af9d21d3b4d664e4a29fcabfec85171077248e3/packages/agent/src/harness/session/state.ts#L61-L97","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","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"],"exampleFix":"// before\nawait session.appendRecord({ id: runId, lane: 'draft', type: 'operation_started', sourceLeafId: null, intent: { kind: 'run', originalPrompt: [], initialMessages: [] } });\n// after\nawait session.createLane('draft', null);\nawait session.appendRecord({ id: runId, lane: 'draft', type: 'operation_started', sourceLeafId: null, intent: { kind: 'run', originalPrompt: [], initialMessages: [] } });","handlingStrategy":"validation","validationCode":"const laneExists = async (session: Session, lane: string): Promise<boolean> =>\n  (await session.getLanes()).some((p) => p.lane === lane);\n\nif (!(await laneExists(session, record.lane))) {\n  await session.createLane(record.lane, null);\n}\nawait session.appendRecord(record);","typeGuard":null,"tryCatchPattern":"try {\n  await session.moveLane('draft', target);\n} catch (error) {\n  if (error instanceof SessionError && error.code === 'invalid_lane') {\n    await session.createLane('draft', null); // then retry, or route the write to 'main'\n  } else {\n    throw error;\n  }\n}","preventionTips":["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"],"tags":["lane","session","not-found","storage"],"backgroundTag":"unknown-lane-reference","analyzedSha":"4af9d21d3b4d664e4a29fcabfec85171077248e3","analyzedAt":"2026-08-24T13:07:14.692Z","schemaVersion":2},"datasetVersion":"2026-08-24T17:17:21.512Z"}