earendil-works/pi · error · SessionError
not_found
not_found
Error message
Session not found: ${id} What it means
Thrown by InMemorySessionRepo.requireStorage when repo.open(metadata) or repo.fork(source) references a session id this repo instance has no storage for. The repo keeps sessions in a plain in-memory Map that is populated only by create(), removed by delete(), and wiped when the process exits, so the id must have been created in the same process and repo instance. It signals a lifecycle mismatch (never created, deleted, or wrong repo), not data corruption.
Source
Thrown at packages/agent/src/harness/session/memory.ts:189
async delete(metadata: SessionMetadata): Promise<void> {
this.sessions.delete(metadata.id);
}
async fork(source: SessionMetadata, options: ForkOptions & SessionCreateOptions = {}): Promise<Session> {
const sourceStorage = this.requireStorage(source.id);
const id = options.id ?? uuidv7();
if (this.sessions.has(id)) throw new SessionError("already_exists", `Session already exists: ${id}`);
const storage = sourceStorage.fork(
{ id, createdAt: Date.now(), parentSessionId: options.parentSessionId ?? source.id },
options,
);
this.sessions.set(id, storage);
return new Session(storage);
}
private requireStorage(id: string): InMemorySessionStorage {
const storage = this.sessions.get(id);
if (!storage) throw new SessionError("not_found", `Session not found: ${id}`);
return storage;
}
}
View on GitHub (pinned to 4af9d21d3b)
Solutions
- Verify the id exists first: const known = (await repo.list()).some((m) => m.id === id), and create it when missing
- Create sessions before opening them: repo.create({ id }) already returns the Session, so open() is only needed for metadata known to this repo
- Use a single repo instance per process and pass it around instead of constructing new ones
- Switch to a durable SessionRepo/SessionStorage implementation when sessions must survive restarts
Example fix
// before
const session = await repo.open({ id: savedId, createdAt: Date.now() });
// after
const meta = (await repo.list()).find((m) => m.id === savedId);
const session = meta ? await repo.open(meta) : await repo.create({ id: savedId }); Defensive patterns
Strategy: validation
Validate before calling
const sessionExists = async (repo: SessionRepo, id: string): Promise<boolean> =>
(await repo.list()).some((m) => m.id === id);
if (!(await sessionExists(repo, savedId))) {
await repo.create({ id: savedId });
} Try / catch
try {
session = await repo.open(meta);
} catch (error) {
if (error instanceof SessionError && error.code === 'not_found') {
session = await repo.create({ id: meta.id }); // or surface 'unknown session' to the caller
} else {
throw error;
}
} Prevention
- Keep one repo instance per process and share it instead of constructing new ones
- Treat InMemorySessionRepo ids as ephemeral; pair them with durable storage before persisting anywhere
- Never open a session after delete() removed it
- Prefer the Session object returned by create()/fork() over reopening by id
When it happens
Trigger: Calling repo.open({ id }) with an id that was never passed to repo.create({ id }); calling open() after repo.delete() removed the session; calling repo.fork(source) where source.id belongs to a different InMemorySessionRepo instance; replaying metadata loaded from disk into a freshly started process.
Common situations: Persisting session metadata while running InMemorySessionRepo, then restarting the process (memory is wiped while the metadata survives); running one repo instance per test or per worker and opening a session created by another; deleting a session in one code path while another still opens it; typos in ids copied from logs.
Related errors
- invalid_lane
- invalid_lane
- not_found
- Agent is already processing. Wait for completion before rese
- invalid_payload
AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24).
Data as JSON: /api/errors/fcdc58181dcea79b.
Report an issue: GitHub.