thedotmack/claude-mem · error
storeObservation requires a non-empty title
Error message
storeObservation requires a non-empty title
What it means
storeObservation is a single-row wrapper around storeObservations. storeObservations silently skips rows with empty titles, which would leave the wrapper with no inserted id to return; to keep its non-nullable {id, createdAtEpoch} contract, it validates the title up front and throws instead.
Solutions
- Set a non-empty observation.title before calling storeObservation (e.g. derive from content or use a placeholder).
- If the title is genuinely optional, call storeObservations with a one-element array instead and check whether an id was returned.
- Add a caller-side guard that falls back to a default title when the source text is empty.
Example fix
// before
await store.storeObservation(sessionId, project, { title, content });
// after
await store.storeObservation(sessionId, project, {
title: title && title.trim() ? title : content.slice(0, 80) || 'untitled observation',
content
}); Defensive patterns
Strategy: validation
Validate before calling
if (!observation.title || observation.title.trim() === '') {
throw new Error('Cannot store observation without a title');
}
await store.storeObservation(sessionId, project, observation); Type guard
function hasTitle(o: { title?: string }): o is { title: string } {
return typeof o.title === 'string' && o.title.trim() !== '';
} Prevention
- Validate observation fields at construction time, not at the store boundary.
- Default the title to a content-derived or placeholder string when sources are empty.
- Keep title required in the observation TypeScript type so it cannot be omitted.
When it happens
Trigger: Calling SessionStore.storeObservation(memorySessionId, project, observation) where observation.title is undefined, null, an empty string, or a whitespace-only string (e.g. title.trim() === '').
Common situations: Building an observation object programmatically where the title extraction failed (e.g. summarizer returned nothing, a hook passed a prompt with no content), or deserializing records from an older DB/export where the title field was optional.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- cloud sync canonical payload
- INVALID_SEARCH_REQUEST
- schema v46: . row is not a positive canonical uint64…
- Adapter rejected input
- agent_event source_id must belong to project_id and team_id
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/a7e434e20ae03580.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sqlite/SessionStore.ts:3039
facts: string[];
narrative: string | null;
concepts: string[];
files_read: string[];
files_modified: string[];
agent_type?: string | null;
agent_id?: string | null;
metadata?: string | null;
},
promptNumber?: number,
discoveryTokens: number = 0,
overrideTimestampEpoch?: number,
generatedByModel?: string
): { id: number; createdAtEpoch: number } {
// storeObservations skips empty-title rows, which would leave no id to return here.
// This wrapper stores exactly one observation, so require a title up front rather than
// returning an undefined id.
if (!observation.title || observation.title.trim() === '') {
throw new Error('storeObservation requires a non-empty title');
}
const result = this.storeObservations(
memorySessionId,
project,
[observation],
null,
promptNumber,
discoveryTokens,
overrideTimestampEpoch,
generatedByModel
);
return { id: result.observationIds[0], createdAtEpoch: result.createdAtEpoch };
}
storeSummary(
memorySessionId: string,View on GitHub (pinned to d8bc9755e7)