{"record":{"id":"7e93ca21a7e0e00b","repo":"earendil-works/pi","slug":"not-found-7e93ca","errorCode":"not_found","errorMessage":"Entry not found: ${targetId}","messagePattern":"Entry not found: (.+?)","errorType":"exception","errorClass":"SessionError","httpStatus":null,"severity":"error","filePath":"packages/agent/src/harness/session/state.ts","lineNumber":89,"sourceCode":"\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 {\n\t\tconst seq =\n\t\t\tmutation.kind === \"entry\"\n\t\t\t\t? mutation.entry.seq\n\t\t\t\t: mutation.kind === \"record\"\n\t\t\t\t\t? mutation.record.seq\n\t\t\t\t\t: mutation.seq;\n\t\tif (seq !== this.sequence + 1) invalid(`has non-consecutive seq ${seq}`);\n\n\t\tswitch (mutation.kind) {\n\t\t\tcase \"entry\": {","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/earendil-works/pi/blob/4af9d21d3b4d664e4a29fcabfec85171077248e3/packages/agent/src/harness/session/state.ts#L71-L107","documentation":"SessionState.validateTarget throws not_found when a non-null targetId is not a known entry id (entriesById lookup, state.ts:88). It guards createLane, moveLane and setLabel, so every lane target and label target must be an existing entry in the same SessionState. Record ids (tool runs, usage) and lane names are not entry ids and are rejected.","triggerScenarios":"moveLane(lane, entryId) where entryId came from findRecords()/findOpenOperations() (a LaneRecord id, not an Entry id); setLabel on an entry id captured from a different session instance; createLane pointing at a leafId taken before the session was rebuilt or forked.","commonSituations":"Confusing record ids with entry ids because getLog() interleaves both kinds; copying ids from a persisted session file into a fresh SessionState; using stale ids after an external prune or compaction removed entries.","solutions":["Resolve target ids from state.findEntries() or getEntry() on the same instance","Use lane leaf ids from getLanes()/requireLane() as move targets","When sourcing ids from getLog(), use only items with kind === 'entry' (item.entry.id)","Catch 'not_found' and re-fetch current leaf ids before retrying once"],"exampleFix":"// before\nconst run = state.findOpenOperations('main')[0]!;\nsession.moveLane('draft', run.id); // record id -> not_found\n\n// after\nconst last = state.findEntries({ type: 'message' }).at(-1)!;\nsession.moveLane('draft', last.id);","handlingStrategy":"validation","validationCode":"// Verify the target is an existing entry before using it\nif (targetId !== null && state.getEntry(targetId) === undefined) {\n  throw new Error('stale target id: ' + targetId);\n}\nsession.moveLane(lane, targetId);","typeGuard":"function isEntryId(state: SessionState, id: string): boolean {\n  return state.getEntry(id) !== undefined;\n}","tryCatchPattern":"try {\n  session.setLabel(targetId, label);\n} catch (e) {\n  if (isSessionError(e, 'not_found')) {\n    const leaf = state.requireLane(lane); // refresh and retry once with a known-good id\n    if (leaf !== null) session.setLabel(leaf, label);\n  } else throw e;\n}","preventionTips":["Only use ids returned by findEntries(), getEntry() or getLanes() on the same SessionState instance","Never feed record ids (findRecords, findOpenOperations, runIds) as entry targets","Refresh cached ids after rehydrating or rebuilding a session"],"tags":["session","entries","not-found","ids"],"backgroundTag":"entity-not-found","analyzedSha":"4af9d21d3b4d664e4a29fcabfec85171077248e3","analyzedAt":"2026-08-24T13:07:14.692Z","schemaVersion":2},"datasetVersion":"2026-08-24T17:17:21.512Z"}